tizen 2.4 release accepted/tizen_2.4_mobile tizen tizen_2.4 accepted/tizen/2.4/mobile/20151029.035616 submit/tizen_2.4/20151028.063249 tizen_2.4_mobile_release
authorjk7744.park <jk7744.park@samsung.com>
Sat, 24 Oct 2015 07:12:39 +0000 (16:12 +0900)
committerjk7744.park <jk7744.park@samsung.com>
Sat, 24 Oct 2015 07:12:39 +0000 (16:12 +0900)
338 files changed:
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
Dockerfile.android [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
README.rst [new file with mode: 0644]
android-config [new file with mode: 0755]
android-make [new file with mode: 0755]
configure.ac [new file with mode: 0644]
contrib/.gitignore [new file with mode: 0644]
contrib/Makefile.am [new file with mode: 0644]
contrib/nghttpx-init.in [new file with mode: 0644]
contrib/nghttpx-logrotate [new file with mode: 0644]
doc/.gitignore [new file with mode: 0644]
doc/Makefile.am [new file with mode: 0644]
doc/README.rst [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/__init__.py [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/breadcrumbs.html [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/footer.html [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/layout.html [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/layout_old.html [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/search.html [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/searchbox.html [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/css/badge_only.css [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/css/theme.css [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/static/js/theme.js [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/theme.conf [new file with mode: 0644]
doc/_themes/sphinx_rtd_theme/versions.html [new file with mode: 0644]
doc/apiref-header.rst [new file with mode: 0644]
doc/asio_http2.h.rst.in [new file with mode: 0644]
doc/building-android-binary.rst.in [new file with mode: 0644]
doc/conf.py.in [new file with mode: 0644]
doc/contribute.rst.in [new file with mode: 0644]
doc/h2load-howto.rst.in [new file with mode: 0644]
doc/h2load.1 [new file with mode: 0644]
doc/h2load.1.rst [new file with mode: 0644]
doc/h2load.h2r [new file with mode: 0644]
doc/index.rst.in [new file with mode: 0644]
doc/libnghttp2_asio.rst.in [new file with mode: 0644]
doc/make.bat [new file with mode: 0644]
doc/mkapiref.py [new file with mode: 0755]
doc/nghttp.1 [new file with mode: 0644]
doc/nghttp.1.rst [new file with mode: 0644]
doc/nghttp.h2r [new file with mode: 0644]
doc/nghttp2.h.rst.in [new file with mode: 0644]
doc/nghttp2ver.h.rst.in [new file with mode: 0644]
doc/nghttpd.1 [new file with mode: 0644]
doc/nghttpd.1.rst [new file with mode: 0644]
doc/nghttpd.h2r [new file with mode: 0644]
doc/nghttpx-howto.rst.in [new file with mode: 0644]
doc/nghttpx.1 [new file with mode: 0644]
doc/nghttpx.1.rst [new file with mode: 0644]
doc/nghttpx.h2r [new file with mode: 0644]
doc/package_README.rst.in [new file with mode: 0644]
doc/python-apiref.rst.in [new file with mode: 0644]
doc/sources/building-android-binary.rst [new file with mode: 0644]
doc/sources/contribute.rst [new file with mode: 0644]
doc/sources/h2load-howto.rst [new file with mode: 0644]
doc/sources/index.rst [new file with mode: 0644]
doc/sources/libnghttp2_asio.rst [new file with mode: 0644]
doc/sources/nghttpx-howto.rst [new file with mode: 0644]
doc/sources/python-apiref.rst [new file with mode: 0644]
doc/sources/tutorial-client.rst [new file with mode: 0644]
doc/sources/tutorial-hpack.rst [new file with mode: 0644]
doc/sources/tutorial-server.rst [new file with mode: 0644]
doc/tutorial-client.rst.in [new file with mode: 0644]
doc/tutorial-hpack.rst.in [new file with mode: 0644]
doc/tutorial-server.rst.in [new file with mode: 0644]
examples/.gitignore [new file with mode: 0644]
examples/Makefile.am [new file with mode: 0644]
examples/asio-sv.cc [new file with mode: 0644]
examples/asio-sv2.cc [new file with mode: 0644]
examples/asio-sv3.cc [new file with mode: 0644]
examples/client.c [new file with mode: 0644]
examples/deflate.c [new file with mode: 0644]
examples/libevent-client.c [new file with mode: 0644]
examples/libevent-server.c [new file with mode: 0644]
examples/tiny-nghttpd.c [new file with mode: 0644]
fedora/spdylay.spec [new file with mode: 0644]
gendowncasetbl.py [new file with mode: 0755]
genheaderfunc.py [new file with mode: 0755]
gennmchartbl.py [new file with mode: 0755]
genvchartbl.py [new file with mode: 0755]
git-clang-format [new file with mode: 0755]
help2rst.py [new file with mode: 0755]
integration-tests/Makefile.am [new file with mode: 0644]
integration-tests/alt-server.crt [new file with mode: 0644]
integration-tests/alt-server.key [new file with mode: 0644]
integration-tests/config.go.in [new file with mode: 0644]
integration-tests/nghttpx_http1_test.go [new file with mode: 0644]
integration-tests/nghttpx_http2_test.go [new file with mode: 0644]
integration-tests/nghttpx_spdy_test.go [new file with mode: 0644]
integration-tests/server.crt [new file with mode: 0644]
integration-tests/server.key [new file with mode: 0644]
integration-tests/server_tester.go [new file with mode: 0644]
integration-tests/setenv.in [new file with mode: 0644]
lib/Makefile.am [new file with mode: 0644]
lib/Makefile.msvc [new file with mode: 0644]
lib/includes/Makefile.am [new file with mode: 0644]
lib/includes/nghttp2/nghttp2.h [new file with mode: 0644]
lib/includes/nghttp2/nghttp2ver.h.in [new file with mode: 0644]
lib/libnghttp2.pc.in [new file with mode: 0644]
lib/nghttp2_buf.c [new file with mode: 0644]
lib/nghttp2_buf.h [new file with mode: 0644]
lib/nghttp2_callbacks.c [new file with mode: 0644]
lib/nghttp2_callbacks.h [new file with mode: 0644]
lib/nghttp2_frame.c [new file with mode: 0644]
lib/nghttp2_frame.h [new file with mode: 0644]
lib/nghttp2_hd.c [new file with mode: 0644]
lib/nghttp2_hd.h [new file with mode: 0644]
lib/nghttp2_hd_huffman.c [new file with mode: 0644]
lib/nghttp2_hd_huffman.h [new file with mode: 0644]
lib/nghttp2_hd_huffman_data.c [new file with mode: 0644]
lib/nghttp2_helper.c [new file with mode: 0644]
lib/nghttp2_helper.h [new file with mode: 0644]
lib/nghttp2_int.h [new file with mode: 0644]
lib/nghttp2_map.c [new file with mode: 0644]
lib/nghttp2_map.h [new file with mode: 0644]
lib/nghttp2_mem.c [new file with mode: 0644]
lib/nghttp2_mem.h [new file with mode: 0644]
lib/nghttp2_net.h [new file with mode: 0644]
lib/nghttp2_npn.c [new file with mode: 0644]
lib/nghttp2_npn.h [new file with mode: 0644]
lib/nghttp2_option.c [new file with mode: 0644]
lib/nghttp2_option.h [new file with mode: 0644]
lib/nghttp2_outbound_item.c [new file with mode: 0644]
lib/nghttp2_outbound_item.h [new file with mode: 0644]
lib/nghttp2_pq.c [new file with mode: 0644]
lib/nghttp2_pq.h [new file with mode: 0644]
lib/nghttp2_priority_spec.c [new file with mode: 0644]
lib/nghttp2_priority_spec.h [new file with mode: 0644]
lib/nghttp2_queue.c [new file with mode: 0644]
lib/nghttp2_queue.h [new file with mode: 0644]
lib/nghttp2_session.c [new file with mode: 0644]
lib/nghttp2_session.h [new file with mode: 0644]
lib/nghttp2_stream.c [new file with mode: 0644]
lib/nghttp2_stream.h [new file with mode: 0644]
lib/nghttp2_submit.c [new file with mode: 0644]
lib/nghttp2_submit.h [new file with mode: 0644]
lib/nghttp2_version.c [new file with mode: 0644]
m4/ax_boost_asio.m4 [new file with mode: 0644]
m4/ax_boost_base.m4 [new file with mode: 0644]
m4/ax_boost_system.m4 [new file with mode: 0644]
m4/ax_boost_thread.m4 [new file with mode: 0644]
m4/ax_check_compile_flag.m4 [new file with mode: 0644]
m4/ax_cxx_compile_stdcxx_11.m4 [new file with mode: 0644]
m4/ax_have_epoll.m4 [new file with mode: 0644]
m4/ax_python_devel.m4 [new file with mode: 0644]
makemanpages [new file with mode: 0755]
makerelease.sh [new file with mode: 0755]
mkcipherlist.py [new file with mode: 0755]
mkhufftbl.py [new file with mode: 0755]
mkstatichdtbl.py [new file with mode: 0755]
nghttpx.conf.sample [new file with mode: 0644]
packaging/nghttp2.spec [new file with mode: 0644]
pre-commit [new file with mode: 0755]
proxy.pac.sample [new file with mode: 0644]
python/Makefile.am [new file with mode: 0644]
python/calcratio.py [new file with mode: 0755]
python/cnghttp2.pxd [new file with mode: 0644]
python/hpackcheck.py [new file with mode: 0755]
python/hpackmake.py [new file with mode: 0755]
python/nghttp2.pyx [new file with mode: 0644]
python/setup.py.in [new file with mode: 0644]
python/wsgi.py [new file with mode: 0644]
src/.gitignore [new file with mode: 0644]
src/HtmlParser.cc [new file with mode: 0644]
src/HtmlParser.h [new file with mode: 0644]
src/HttpServer.cc [new file with mode: 0644]
src/HttpServer.h [new file with mode: 0644]
src/Makefile.am [new file with mode: 0644]
src/app_helper.cc [new file with mode: 0644]
src/app_helper.h [new file with mode: 0644]
src/asio_connection.h [new file with mode: 0644]
src/asio_http2_handler.cc [new file with mode: 0644]
src/asio_http2_handler.h [new file with mode: 0644]
src/asio_http2_impl.cc [new file with mode: 0644]
src/asio_http2_impl.h [new file with mode: 0644]
src/asio_io_service_pool.cc [new file with mode: 0644]
src/asio_io_service_pool.h [new file with mode: 0644]
src/asio_server.cc [new file with mode: 0644]
src/asio_server.h [new file with mode: 0644]
src/base64.h [new file with mode: 0644]
src/buffer.h [new file with mode: 0644]
src/buffer_test.cc [new file with mode: 0644]
src/buffer_test.h [new file with mode: 0644]
src/comp_helper.c [new file with mode: 0644]
src/comp_helper.h [new file with mode: 0644]
src/deflatehd.cc [new file with mode: 0644]
src/h2load.cc [new file with mode: 0644]
src/h2load.h [new file with mode: 0644]
src/h2load_http2_session.cc [new file with mode: 0644]
src/h2load_http2_session.h [new file with mode: 0644]
src/h2load_session.h [new file with mode: 0644]
src/h2load_spdy_session.cc [new file with mode: 0644]
src/h2load_spdy_session.h [new file with mode: 0644]
src/http-parser.patch [new file with mode: 0644]
src/http2.cc [new file with mode: 0644]
src/http2.h [new file with mode: 0644]
src/http2_test.cc [new file with mode: 0644]
src/http2_test.h [new file with mode: 0644]
src/includes/Makefile.am [new file with mode: 0644]
src/includes/nghttp2/asio_http2.h [new file with mode: 0644]
src/inflatehd.cc [new file with mode: 0644]
src/libevent_util.cc [new file with mode: 0644]
src/libevent_util.h [new file with mode: 0644]
src/libnghttp2_asio.pc.in [new file with mode: 0644]
src/memchunk.h [new file with mode: 0644]
src/memchunk_test.cc [new file with mode: 0644]
src/memchunk_test.h [new file with mode: 0644]
src/nghttp.cc [new file with mode: 0644]
src/nghttp.h [new file with mode: 0644]
src/nghttp2_config.h [new file with mode: 0644]
src/nghttp2_gzip.c [new file with mode: 0644]
src/nghttp2_gzip.h [new file with mode: 0644]
src/nghttp2_gzip_test.c [new file with mode: 0644]
src/nghttp2_gzip_test.h [new file with mode: 0644]
src/nghttpd.cc [new file with mode: 0644]
src/shrpx-unittest.cc [new file with mode: 0644]
src/shrpx.cc [new file with mode: 0644]
src/shrpx.h [new file with mode: 0644]
src/shrpx_accept_handler.cc [new file with mode: 0644]
src/shrpx_accept_handler.h [new file with mode: 0644]
src/shrpx_client_handler.cc [new file with mode: 0644]
src/shrpx_client_handler.h [new file with mode: 0644]
src/shrpx_config.cc [new file with mode: 0644]
src/shrpx_config.h [new file with mode: 0644]
src/shrpx_config_test.cc [new file with mode: 0644]
src/shrpx_config_test.h [new file with mode: 0644]
src/shrpx_connect_blocker.cc [new file with mode: 0644]
src/shrpx_connect_blocker.h [new file with mode: 0644]
src/shrpx_connection.cc [new file with mode: 0644]
src/shrpx_connection.h [new file with mode: 0644]
src/shrpx_connection_handler.cc [new file with mode: 0644]
src/shrpx_connection_handler.h [new file with mode: 0644]
src/shrpx_downstream.cc [new file with mode: 0644]
src/shrpx_downstream.h [new file with mode: 0644]
src/shrpx_downstream_connection.cc [new file with mode: 0644]
src/shrpx_downstream_connection.h [new file with mode: 0644]
src/shrpx_downstream_connection_pool.cc [new file with mode: 0644]
src/shrpx_downstream_connection_pool.h [new file with mode: 0644]
src/shrpx_downstream_queue.cc [new file with mode: 0644]
src/shrpx_downstream_queue.h [new file with mode: 0644]
src/shrpx_downstream_test.cc [new file with mode: 0644]
src/shrpx_downstream_test.h [new file with mode: 0644]
src/shrpx_error.h [new file with mode: 0644]
src/shrpx_http.cc [new file with mode: 0644]
src/shrpx_http.h [new file with mode: 0644]
src/shrpx_http2_downstream_connection.cc [new file with mode: 0644]
src/shrpx_http2_downstream_connection.h [new file with mode: 0644]
src/shrpx_http2_session.cc [new file with mode: 0644]
src/shrpx_http2_session.h [new file with mode: 0644]
src/shrpx_http2_upstream.cc [new file with mode: 0644]
src/shrpx_http2_upstream.h [new file with mode: 0644]
src/shrpx_http_downstream_connection.cc [new file with mode: 0644]
src/shrpx_http_downstream_connection.h [new file with mode: 0644]
src/shrpx_https_upstream.cc [new file with mode: 0644]
src/shrpx_https_upstream.h [new file with mode: 0644]
src/shrpx_io_control.cc [new file with mode: 0644]
src/shrpx_io_control.h [new file with mode: 0644]
src/shrpx_log.cc [new file with mode: 0644]
src/shrpx_log.h [new file with mode: 0644]
src/shrpx_rate_limit.cc [new file with mode: 0644]
src/shrpx_rate_limit.h [new file with mode: 0644]
src/shrpx_spdy_upstream.cc [new file with mode: 0644]
src/shrpx_spdy_upstream.h [new file with mode: 0644]
src/shrpx_ssl.cc [new file with mode: 0644]
src/shrpx_ssl.h [new file with mode: 0644]
src/shrpx_ssl_test.cc [new file with mode: 0644]
src/shrpx_ssl_test.h [new file with mode: 0644]
src/shrpx_upstream.h [new file with mode: 0644]
src/shrpx_worker.cc [new file with mode: 0644]
src/shrpx_worker.h [new file with mode: 0644]
src/shrpx_worker_config.cc [new file with mode: 0644]
src/shrpx_worker_config.h [new file with mode: 0644]
src/ssl.cc [new file with mode: 0644]
src/ssl.h [new file with mode: 0644]
src/template.h [new file with mode: 0644]
src/timegm.c [new file with mode: 0644]
src/timegm.h [new file with mode: 0644]
src/util.cc [new file with mode: 0644]
src/util.h [new file with mode: 0644]
src/util_test.cc [new file with mode: 0644]
src/util_test.h [new file with mode: 0644]
tests/.gitignore [new file with mode: 0644]
tests/Makefile.am [new file with mode: 0644]
tests/end_to_end.py [new file with mode: 0755]
tests/failmalloc.c [new file with mode: 0644]
tests/failmalloc_test.c [new file with mode: 0644]
tests/failmalloc_test.h [new file with mode: 0644]
tests/main.c [new file with mode: 0644]
tests/malloc_wrapper.c [new file with mode: 0644]
tests/malloc_wrapper.h [new file with mode: 0644]
tests/nghttp2_buf_test.c [new file with mode: 0644]
tests/nghttp2_buf_test.h [new file with mode: 0644]
tests/nghttp2_frame_test.c [new file with mode: 0644]
tests/nghttp2_frame_test.h [new file with mode: 0644]
tests/nghttp2_hd_test.c [new file with mode: 0644]
tests/nghttp2_hd_test.h [new file with mode: 0644]
tests/nghttp2_helper_test.c [new file with mode: 0644]
tests/nghttp2_helper_test.h [new file with mode: 0644]
tests/nghttp2_map_test.c [new file with mode: 0644]
tests/nghttp2_map_test.h [new file with mode: 0644]
tests/nghttp2_npn_test.c [new file with mode: 0644]
tests/nghttp2_npn_test.h [new file with mode: 0644]
tests/nghttp2_pq_test.c [new file with mode: 0644]
tests/nghttp2_pq_test.h [new file with mode: 0644]
tests/nghttp2_queue_test.c [new file with mode: 0644]
tests/nghttp2_queue_test.h [new file with mode: 0644]
tests/nghttp2_session_test.c [new file with mode: 0644]
tests/nghttp2_session_test.h [new file with mode: 0644]
tests/nghttp2_stream_test.c [new file with mode: 0644]
tests/nghttp2_stream_test.h [new file with mode: 0644]
tests/nghttp2_test_helper.c [new file with mode: 0644]
tests/nghttp2_test_helper.h [new file with mode: 0644]
tests/testdata/Makefile.am [new file with mode: 0644]
tests/testdata/cacert.pem [new file with mode: 0644]
tests/testdata/index.html [new file with mode: 0644]
tests/testdata/privkey.pem [new file with mode: 0644]
third-party/Makefile.am [new file with mode: 0644]
third-party/http-parser/AUTHORS [new file with mode: 0644]
third-party/http-parser/CONTRIBUTIONS [new file with mode: 0644]
third-party/http-parser/LICENSE-MIT [new file with mode: 0644]
third-party/http-parser/README.md [new file with mode: 0644]
third-party/http-parser/bench.c [new file with mode: 0644]
third-party/http-parser/contrib/parsertrace.c [new file with mode: 0644]
third-party/http-parser/contrib/url_parser.c [new file with mode: 0644]
third-party/http-parser/http_parser.c [new file with mode: 0644]
third-party/http-parser/http_parser.gyp [new file with mode: 0644]
third-party/http-parser/http_parser.h [new file with mode: 0644]
third-party/http-parser/test.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..95bc954
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Tatsuhiro Tsujikawa <t-tujikawa at users dot sourceforge dot net>
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..02ea621
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,41 @@
+The MIT License
+
+Copyright (c) 2012, 2014 Tatsuhiro Tsujikawa
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+[The text below was composed based on 1.2. License section of
+curl/libcurl project.]
+
+When contributing with code, you agree to put your changes and new
+code under the same license nghttp2 is already using unless stated and
+agreed otherwise.
+
+When changing existing source code, you do not alter the copyright of
+the original file(s).  The copyright will still be owned by the
+original creator(s) or those who have been assigned copyright by the
+original author(s).
+
+By submitting a patch to the nghttp2 project, you are assumed to have
+the right to the code and to be allowed by your employer or whatever
+to hand over that patch/code to us.  We will credit you for your
+changes as far as possible, to give credit but also to keep a trace
+back to who made what changes.  Please always provide us with your
+full real name when contributing!
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Dockerfile.android b/Dockerfile.android
new file mode 100644 (file)
index 0000000..5fd2409
--- /dev/null
@@ -0,0 +1,103 @@
+# Dockerfile to build nghttp2 android binary
+#
+# $ sudo docker build -t nghttp2-android - < Dockerfile.android
+#
+# After successful build, android binaries are located under
+# /root/build/nghttp2.  You can copy the binary using docker cp.  For
+# example, to copy nghttpx binary to host file system location
+# /path/to/dest, do this:
+#
+# $ sudo docker run -v /path/to/dest:/out nghttp2-android cp /root/build/nghttp2/src/nghttpx /out
+
+FROM ubuntu
+
+MAINTAINER Tatsuhiro Tsujikawa
+
+ENV ANDROID_HOME /root/android
+ENV PREFIX $ANDROID_HOME/usr/local
+ENV TOOLCHAIN $ANDROID_HOME/toolchain
+ENV PATH $TOOLCHAIN/bin:$PATH
+
+# It would be better to use nearest ubuntu archive mirror for faster
+# downloads.
+# RUN sed -ie 's/archive\.ubuntu/jp.archive.ubuntu/g' /etc/apt/sources.list
+
+RUN apt-get update
+# genisoimage, libc6-i386 and lib32stdc++6 are required to decompress ndk.
+RUN apt-get install -y make binutils autoconf automake autotools-dev libtool \
+    pkg-config git curl dpkg-dev libxml2-dev \
+    genisoimage libc6-i386 lib32stdc++6
+
+WORKDIR /root/build
+RUN curl -L -O http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin
+RUN chmod a+x android-ndk-r10c-linux-x86_64.bin
+RUN ./android-ndk-r10c-linux-x86_64.bin
+
+WORKDIR /root/build/android-ndk-r10c
+RUN /bin/bash build/tools/make-standalone-toolchain.sh \
+    --install-dir=$ANDROID_HOME/toolchain \
+    --toolchain=arm-linux-androideabi-4.9 --llvm-version=3.5 \
+    --system=linux-x86_64
+
+WORKDIR /root/build
+RUN git clone https://github.com/tatsuhiro-t/spdylay
+WORKDIR /root/build/spdylay
+RUN autoreconf -i && \
+    ./configure \
+    --disable-shared \
+    --host=arm-linux-androideabi \
+    --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+    --prefix=$PREFIX \
+    --without-libxml2 \
+    --disable-src \
+    --disable-examples \
+    CPPFLAGS="-I$PREFIX/include" \
+    PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
+    LDFLAGS="-L$PREFIX/lib" && \
+    make install
+
+WORKDIR /root/build
+RUN curl -L -O https://www.openssl.org/source/openssl-1.0.1j.tar.gz
+RUN tar xf openssl-1.0.1j.tar.gz
+WORKDIR /root/build/openssl-1.0.1j
+RUN export CROSS_COMPILE=$TOOLCHAIN/bin/arm-linux-androideabi- && \
+    ./Configure --prefix=$PREFIX android && \
+    make && make install_sw
+
+WORKDIR /root/build
+RUN curl -L -O http://dist.schmorp.de/libev/libev-4.19.tar.gz
+RUN curl -L -O https://gist.github.com/tatsuhiro-t/48c45f08950f587180ed/raw/80a8f003b5d1091eae497c5995bbaa68096e739b/libev-4.19-android.patch
+RUN tar xf libev-4.19.tar.gz
+WORKDIR /root/build/libev-4.19
+RUN patch -p1 < ../libev-4.19-android.patch
+RUN ./configure \
+    --host=arm-linux-androideabi \
+    --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+    --prefix=$PREFIX \
+    --disable-shared \
+    --enable-static \
+    CPPFLAGS=-I$PREFIX/include \
+    LDFLAGS=-L$PREFIX/lib && \
+    make install
+
+WORKDIR /root/build
+RUN git clone https://github.com/tatsuhiro-t/nghttp2
+WORKDIR /root/build/nghttp2
+RUN autoreconf -i && \
+    ./configure \
+    --disable-shared \
+    --host=arm-linux-androideabi \
+    --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+    --with-xml-prefix="$PREFIX" \
+    --without-libxml2 \
+    --disable-python-bindings \
+    --disable-examples \
+    --disable-threads \
+    LIBSPDYLAY_CFLAGS=-I$PREFIX/usr/local/include \
+    LIBSPDYLAY_LIBS="-L$PREFIX/usr/local/lib -lspdylay" \
+    CPPFLAGS="-I$PREFIX/include" \
+    CXXFLAGS="-fno-strict-aliasing" \
+    PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
+    LDFLAGS="-L$PREFIX/lib" && \
+    make && \
+    arm-linux-androideabi-strip src/nghttpx src/nghttpd src/nghttp
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..1058e02
--- /dev/null
@@ -0,0 +1,43 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS = lib third-party src examples python tests integration-tests \
+       doc contrib
+
+ACLOCAL_AMFLAGS = -I m4
+
+dist_doc_DATA = README.rst
+
+EXTRA_DIST = nghttpx.conf.sample proxy.pac.sample android-config android-make \
+       Dockerfile.android
+
+.PHONY: clang-format
+
+# Format source files using clang-format.  Don't format source files
+# under third-party directory since we are not responsible for thier
+# coding style.
+clang-format:
+       CLANGFORMAT=`git config --get clangformat.binary`; \
+       test -z $${CLANGFORMAT} && CLANGFORMAT="clang-format"; \
+       $${CLANGFORMAT} -i lib/*.{c,h} lib/includes/nghttp2/*.h \
+       src/*.{c,cc,h} src/includes/nghttp2/*.h examples/*.{c,cc} \
+       tests/*.{c,h}
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..5ccc0ea
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+See README.rst
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..7b6ec23
--- /dev/null
@@ -0,0 +1,1192 @@
+nghttp2 - HTTP/2 C Library
+==========================
+
+This is an implementation of Hypertext Transfer Protocol version 2
+in C.
+
+The framing layer of HTTP/2 is implemented as a form of reusable C
+library.  On top of that, we have implemented HTTP/2 client, server
+and proxy.  We have also developed load test and benchmarking tool for
+HTTP/2 and SPDY.
+
+HPACK encoder and decoder are available as public API.
+
+The experimental high level C++ library is also available.
+
+We have Python binding of this libary, but we have not covered
+everything yet.
+
+Development Status
+------------------
+
+We started to implement h2-14
+(http://tools.ietf.org/html/draft-ietf-httpbis-http2-14), the header
+compression
+(http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09).
+
+The nghttp2 code base was forked from spdylay project.
+
+=========================== =======
+HTTP/2 Features             Support
+=========================== =======
+Core frames handling        Yes
+Dependency Tree             Yes
+Large header (CONTINUATION) Yes
+=========================== =======
+
+Public Test Server
+------------------
+
+The following endpoints are available to try out nghttp2
+implementation.
+
+* https://nghttp2.org/ (TLS + ALPN/NPN)
+
+  NPN offer ``h2-14``, ``spdy/3.1`` and ``http/1.1``.
+
+  This endpoint requires TLSv1.2 for HTTP/2 connection.
+
+* http://nghttp2.org/ (Upgrade / Direct)
+
+  ``h2c-14`` and ``http/1.1``.
+
+Requirements
+------------
+
+The following package is required to build the libnghttp2 library:
+
+* pkg-config >= 0.20
+
+To build and run the unit test programs, the following package is
+required:
+
+* cunit >= 2.1
+
+To build the documentation, you need to install:
+
+* sphinx (http://sphinx-doc.org/)
+
+To build and run the application programs (``nghttp``, ``nghttpd`` and
+``nghttpx``) in ``src`` directory, the following packages are
+required:
+
+* OpenSSL >= 1.0.1
+* libev >= 4.15
+* zlib >= 1.2.3
+
+ALPN support requires unreleased version OpenSSL >= 1.0.2.
+
+To enable SPDY protocol in the application program ``nghttpx`` and
+``h2load``, the following package is required:
+
+* spdylay >= 1.3.0
+
+To enable ``-a`` option (getting linked assets from the downloaded
+resource) in ``nghttp``, the following package is required:
+
+* libxml2 >= 2.7.7
+
+The HPACK tools require the following package:
+
+* jansson >= 2.5
+
+To build sources under examples directory, libevent is required:
+
+* libevent-openssl >= 2.0.8
+
+To mitigate heap fragmentation in long running server programs
+(``nghttpd`` and ``nghttpx``), jemalloc is recommended:
+
+* jemalloc
+
+libnghttp2_asio C++ library requires the following packages:
+
+* libboost-dev >= 1.54.0
+* libboost-thread-dev >= 1.54.0
+
+The Python bindings require the following packages:
+
+* cython >= 0.19
+* python >= 2.7
+
+If you are using Ubuntu 14.04 LTS, you need the following packages
+installed::
+
+    apt-get install make binutils autoconf  automake autotools-dev libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev libevent-dev libjansson-dev libjemalloc-dev cython python3.4-dev
+
+spdylay is not packaged in Ubuntu, so you need to build it yourself:
+http://tatsuhiro-t.github.io/spdylay/
+
+Build from git
+--------------
+
+Building from git is easy, but please be sure that at least autoconf 2.68 is
+used::
+
+    $ autoreconf -i
+    $ automake
+    $ autoconf
+    $ ./configure
+    $ make
+
+To compile source code, gcc >= 4.8.3 or clang >= 3.4 is required.
+
+.. note::
+
+   Mac OS X users may need ``--disable-threads`` configure option to
+   disable multi threading in nghttpd, nghttpx and h2load to prevent
+   them from crashing.  Patch is welcome to make multi threading work
+   on Mac OS X platform.
+
+Building documentation
+----------------------
+
+.. note::
+
+   Documentation is still incomplete.
+
+To build documentation, run::
+
+    $ make html
+
+The documents will be generated under ``doc/manual/html/``.
+
+The generated documents will not be installed with ``make install``.
+
+The online documentation is available at
+https://nghttp2.org/documentation/
+
+Unit tests
+----------
+
+Unit tests are done by simply running `make check`.
+
+Integration tests
+-----------------
+
+We have the integration tests for nghttpx proxy server.  The tests are
+written in `Go programming language <http://golang.org/>`_ and uses
+its testing framework.  We depend on the following libraries:
+
+* https://github.com/bradfitz/http2
+* https://github.com/tatsuhiro-t/go-nghttp2
+* https://golang.org/x/net/spdy
+
+To download the above packages, after settings ``GOPATH``, run the
+following command under ``integration-tests`` directory::
+
+    $ make itprep
+
+To run the tests, run the following command under
+``integration-tests`` directory::
+
+    $ make it
+
+Inside the tests, we use port 3009 to run test subject server.
+
+Client, Server and Proxy programs
+---------------------------------
+
+The src directory contains HTTP/2 client, server and proxy programs.
+
+nghttp - client
++++++++++++++++
+
+``nghttp`` is a HTTP/2 client.  It can connect to the HTTP/2 server
+with prior knowledge, HTTP Upgrade and NPN/ALPN TLS extension.
+
+It has verbose output mode for framing information.  Here is sample
+output from ``nghttp`` client::
+
+    $ nghttp -nv https://nghttp2.org
+    [  0.033][NPN] server offers:
+              * h2-14
+              * spdy/3.1
+              * http/1.1
+    The negotiated protocol: h2-14
+    [  0.068] send SETTINGS frame <length=15, flags=0x00, stream_id=0>
+              (niv=3)
+              [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
+              [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
+              [SETTINGS_COMPRESS_DATA(5):1]
+    [  0.068] send HEADERS frame <length=46, flags=0x05, stream_id=1>
+              ; END_STREAM | END_HEADERS
+              (padlen=0)
+              ; Open new stream
+              :authority: nghttp2.org
+              :method: GET
+              :path: /
+              :scheme: https
+              accept: */*
+              accept-encoding: gzip, deflate
+              user-agent: nghttp2/0.4.0-DEV
+    [  0.068] recv SETTINGS frame <length=10, flags=0x00, stream_id=0>
+              (niv=2)
+              [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
+              [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
+    [  0.068] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
+              ; ACK
+              (niv=0)
+    [  0.079] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
+              ; ACK
+              (niv=0)
+    [  0.080] (stream_id=1, noind=0) :status: 200
+    [  0.080] (stream_id=1, noind=0) accept-ranges: bytes
+    [  0.080] (stream_id=1, noind=0) age: 15
+    [  0.080] (stream_id=1, noind=0) content-length: 40243
+    [  0.080] (stream_id=1, noind=0) content-type: text/html
+    [  0.080] (stream_id=1, noind=0) date: Wed, 14 May 2014 15:14:30 GMT
+    [  0.080] (stream_id=1, noind=0) etag: "535d0eea-9d33"
+    [  0.080] (stream_id=1, noind=0) last-modified: Sun, 27 Apr 2014 14:06:34 GMT
+    [  0.080] (stream_id=1, noind=0) server: nginx/1.4.6 (Ubuntu)
+    [  0.080] (stream_id=1, noind=0) x-varnish: 2114900538 2114900537
+    [  0.080] (stream_id=1, noind=0) via: 1.1 varnish, 1.1 nghttpx
+    [  0.080] (stream_id=1, noind=0) strict-transport-security: max-age=31536000
+    [  0.080] recv HEADERS frame <length=162, flags=0x04, stream_id=1>
+              ; END_HEADERS
+              (padlen=0)
+              ; First response header
+    [  0.080] recv DATA frame <length=3786, flags=0x00, stream_id=1>
+    [  0.080] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.081] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.093] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.093] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.094] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.094] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.094] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.096] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.096] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
+              (window_size_increment=36554)
+    [  0.096] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
+              (window_size_increment=36554)
+    [  0.108] recv DATA frame <length=3689, flags=0x00, stream_id=1>
+    [  0.108] recv DATA frame <length=0, flags=0x01, stream_id=1>
+              ; END_STREAM
+    [  0.108] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
+              (last_stream_id=0, error_code=NO_ERROR(0), opaque_data(0)=[])
+
+The HTTP Upgrade is performed like this::
+
+    $ nghttp -nvu http://nghttp2.org
+    [  0.013] HTTP Upgrade request
+    GET / HTTP/1.1
+    Host: nghttp2.org
+    Connection: Upgrade, HTTP2-Settings
+    Upgrade: h2c-14
+    HTTP2-Settings: AwAAAGQEAAD__wUAAAAB
+    Accept: */*
+    User-Agent: nghttp2/0.4.0-DEV
+
+
+    [  0.024] HTTP Upgrade response
+    HTTP/1.1 101 Switching Protocols
+    Connection: Upgrade
+    Upgrade: h2c-14
+
+
+    [  0.024] HTTP Upgrade success
+    [  0.024] send SETTINGS frame <length=15, flags=0x00, stream_id=0>
+              (niv=3)
+              [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
+              [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
+              [SETTINGS_COMPRESS_DATA(5):1]
+    [  0.024] recv SETTINGS frame <length=10, flags=0x00, stream_id=0>
+              (niv=2)
+              [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
+              [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
+    [  0.024] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
+              ; ACK
+              (niv=0)
+    [  0.024] (stream_id=1, noind=0) :status: 200
+    [  0.024] (stream_id=1, noind=0) accept-ranges: bytes
+    [  0.024] (stream_id=1, noind=0) age: 10
+    [  0.024] (stream_id=1, noind=0) content-length: 40243
+    [  0.024] (stream_id=1, noind=0) content-type: text/html
+    [  0.024] (stream_id=1, noind=0) date: Wed, 14 May 2014 15:16:34 GMT
+    [  0.024] (stream_id=1, noind=0) etag: "535d0eea-9d33"
+    [  0.024] (stream_id=1, noind=0) last-modified: Sun, 27 Apr 2014 14:06:34 GMT
+    [  0.024] (stream_id=1, noind=0) server: nginx/1.4.6 (Ubuntu)
+    [  0.024] (stream_id=1, noind=0) x-varnish: 2114900541 2114900540
+    [  0.024] (stream_id=1, noind=0) via: 1.1 varnish, 1.1 nghttpx
+    [  0.024] recv HEADERS frame <length=148, flags=0x04, stream_id=1>
+              ; END_HEADERS
+              (padlen=0)
+              ; First response header
+    [  0.024] recv DATA frame <length=3786, flags=0x00, stream_id=1>
+    [  0.025] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.031] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.031] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.032] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.032] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.033] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.033] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.033] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=0>
+              (window_size_increment=33164)
+    [  0.033] send WINDOW_UPDATE frame <length=4, flags=0x00, stream_id=1>
+              (window_size_increment=33164)
+    [  0.038] recv DATA frame <length=4096, flags=0x00, stream_id=1>
+    [  0.038] recv DATA frame <length=3689, flags=0x00, stream_id=1>
+    [  0.038] recv DATA frame <length=0, flags=0x01, stream_id=1>
+              ; END_STREAM
+    [  0.038] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
+              ; ACK
+              (niv=0)
+    [  0.038] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
+              (last_stream_id=0, error_code=NO_ERROR(0), opaque_data(0)=[])
+
+With ``-s`` option, ``nghttp`` prints out some timing information for
+requests, sorted by completion time::
+
+    $ nghttp -nas https://nghttp2.org/
+    ***** Statistics *****
+
+    Request timing:
+      complete: relative time from protocol handshake to stream close
+       request: relative   time  from   protocol   handshake  to   request
+               transmission
+       process: time for request and response
+         code: HTTP status code
+          URI: request URI
+
+    sorted by 'complete'
+
+    complete  request   process  code request path
+     +17.37ms    +104us  17.27ms  200 /
+     +17.50ms   +8.15ms   9.35ms  200 /javascripts/octopress.js
+     +22.06ms   +8.15ms  13.91ms  200 /javascripts/modernizr-2.0.js
+     +27.07ms   +8.15ms  18.92ms  200 /stylesheets/screen.css
+     +98.57ms  +17.55ms  81.02ms  200 /images/posts/with-pri-blog.png
+    +104.70ms  +17.55ms  87.15ms  200 /images/posts/without-pri-blog.png
+
+With ``-r`` option, ``nghttp`` writes more detailed timing data to
+given file in HAR format.
+
+nghttpd - server
+++++++++++++++++
+
+``nghttpd`` is a multi-threaded static web server.
+
+By default, it uses SSL/TLS connection.  Use ``--no-tls`` option to
+disable it.
+
+``nghttpd`` only accepts the HTTP/2 connection via NPN/ALPN or direct
+HTTP/2 connection.  No HTTP Upgrade is supported.
+
+``-p`` option allows users to configure server push.
+
+Just like ``nghttp``, it has verbose output mode for framing
+information.  Here is sample output from ``nghttpd`` server::
+
+    $ nghttpd --no-tls -v 8080
+    IPv4: listen on port 8080
+    IPv6: listen on port 8080
+    [id=1] [ 15.921] send SETTINGS frame <length=10, flags=0x00, stream_id=0>
+              (niv=2)
+              [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
+              [SETTINGS_COMPRESS_DATA(5):1]
+    [id=1] [ 15.921] recv SETTINGS frame <length=15, flags=0x00, stream_id=0>
+              (niv=3)
+              [SETTINGS_MAX_CONCURRENT_STREAMS(3):100]
+              [SETTINGS_INITIAL_WINDOW_SIZE(4):65535]
+              [SETTINGS_COMPRESS_DATA(5):1]
+    [id=1] [ 15.921] (stream_id=1, noind=0) :authority: localhost:8080
+    [id=1] [ 15.921] (stream_id=1, noind=0) :method: GET
+    [id=1] [ 15.921] (stream_id=1, noind=0) :path: /
+    [id=1] [ 15.921] (stream_id=1, noind=0) :scheme: http
+    [id=1] [ 15.921] (stream_id=1, noind=0) accept: */*
+    [id=1] [ 15.921] (stream_id=1, noind=0) accept-encoding: gzip, deflate
+    [id=1] [ 15.921] (stream_id=1, noind=0) user-agent: nghttp2/0.4.0-DEV
+    [id=1] [ 15.921] recv HEADERS frame <length=48, flags=0x05, stream_id=1>
+              ; END_STREAM | END_HEADERS
+              (padlen=0)
+              ; Open new stream
+    [id=1] [ 15.921] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
+              ; ACK
+              (niv=0)
+    [id=1] [ 15.921] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
+              ; ACK
+              (niv=0)
+    [id=1] [ 15.921] send HEADERS frame <length=82, flags=0x04, stream_id=1>
+              ; END_HEADERS
+              (padlen=0)
+              ; First response header
+              :status: 200
+              cache-control: max-age=3600
+              content-length: 612
+              date: Wed, 14 May 2014 15:19:03 GMT
+              last-modified: Sat, 08 Mar 2014 16:04:06 GMT
+              server: nghttpd nghttp2/0.4.0-DEV
+    [id=1] [ 15.922] send DATA frame <length=381, flags=0x20, stream_id=1>
+              ; COMPRESSED
+    [id=1] [ 15.922] send DATA frame <length=0, flags=0x01, stream_id=1>
+              ; END_STREAM
+    [id=1] [ 15.922] stream_id=1 closed
+    [id=1] [ 15.922] recv GOAWAY frame <length=8, flags=0x00, stream_id=0>
+              (last_stream_id=0, error_code=NO_ERROR(0), opaque_data(0)=[])
+    [id=1] [ 15.922] closed
+
+nghttpx - proxy
++++++++++++++++
+
+``nghttpx`` is a multi-threaded reverse proxy for ``h2-14``, SPDY and
+HTTP/1.1 and powers nghttp2.org site.  It has several operation modes:
+
+================== ============================ ============== =============
+Mode option        Frontend                     Backend        Note
+================== ============================ ============== =============
+default mode       HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/1.1       Reverse proxy
+``--http2-proxy``  HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/1.1       SPDY proxy
+``--http2-bridge`` HTTP/2, SPDY, HTTP/1.1 (TLS) HTTP/2 (TLS)
+``--client``       HTTP/2, HTTP/1.1             HTTP/2 (TLS)
+``--client-proxy`` HTTP/2, HTTP/1.1             HTTP/2 (TLS)   Forward proxy
+================== ============================ ============== =============
+
+The interesting mode at the moment is the default mode.  It works like
+a reverse proxy and listens for ``h2-14``, SPDY and HTTP/1.1 and can
+be deployed SSL/TLS terminator for existing web server.
+
+The default mode, ``--http2-proxy`` and ``--http2-bridge`` modes use
+SSL/TLS in the frontend connection by default.  To disable SSL/TLS,
+use ``--frontend-no-tls`` option.  If that option is used, SPDY is
+disabled in the frontend and incoming HTTP/1.1 connection can be
+upgraded to HTTP/2 through HTTP Upgrade.
+
+The ``--http2-bridge``, ``--client`` and ``--client-proxy`` modes use
+SSL/TLS in the backend connection by deafult.  To disable SSL/TLS, use
+``--backend-no-tls`` option.
+
+``nghttpx`` supports configuration file.  See ``--conf`` option and
+sample configuration file ``nghttpx.conf.sample``.
+
+``nghttpx`` does not support server push.
+
+In the default mode, (without any of ``--http2-proxy``,
+``--http2-bridge``, ``--client-proxy`` and ``--client`` options),
+``nghttpx`` works as reverse proxy to the backend server::
+
+    Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Web Server
+                                          [reverse proxy]
+
+With ``--http2-proxy`` option, it works as so called secure proxy (aka
+SPDY proxy)::
+
+    Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/1.1) --> Proxy
+                                           [secure proxy]          (e.g., Squid, ATS)
+
+The ``Client`` in the above needs to be configured to use
+``nghttpx`` as secure proxy.
+
+At the time of this writing, Chrome is the only browser which supports
+secure proxy.  The one way to configure Chrome to use secure proxy is
+create proxy.pac script like this:
+
+.. code-block:: javascript
+
+    function FindProxyForURL(url, host) {
+        return "HTTPS SERVERADDR:PORT";
+    }
+
+``SERVERADDR`` and ``PORT`` is the hostname/address and port of the
+machine nghttpx is running.  Please note that Chrome requires valid
+certificate for secure proxy.
+
+Then run Chrome with the following arguments::
+
+    $ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn
+
+With ``--http2-bridge``, it accepts HTTP/2, SPDY and HTTP/1.1
+connections and communicates with backend in HTTP/2::
+
+    Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/2) --> Web or HTTP/2 Proxy etc
+                                                                         (e.g., nghttpx -s)
+
+With ``--client-proxy`` option, it works as forward proxy and expects
+that the backend is HTTP/2 proxy::
+
+    Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) --> HTTP/2 Proxy
+                                     [forward proxy]               (e.g., nghttpx -s)
+
+The ``Client`` needs to be configured to use nghttpx as forward
+proxy.  The frontend HTTP/1.1 connection can be upgraded to HTTP/2
+through HTTP Upgrade.  With the above configuration, one can use
+HTTP/1.1 client to access and test their HTTP/2 servers.
+
+With ``--client`` option, it works as reverse proxy and expects that
+the backend is HTTP/2 Web server::
+
+    Client <-- (HTTP/2, HTTP/1.1) --> nghttpx <-- (HTTP/2) --> Web Server
+                                    [reverse proxy]
+
+The frontend HTTP/1.1 connection can be upgraded to HTTP/2
+through HTTP Upgrade.
+
+For the operation modes which talk to the backend in HTTP/2 over
+SSL/TLS, the backend connections can be tunneled through HTTP proxy.
+The proxy is specified using ``--backend-http-proxy-uri`` option.  The
+following figure illustrates the example of ``--http2-bridge`` and
+``--backend-http-proxy-uri`` options to talk to the outside HTTP/2
+proxy through HTTP proxy::
+
+    Client <-- (HTTP/2, SPDY, HTTP/1.1) --> nghttpx <-- (HTTP/2) --
+
+            --===================---> HTTP/2 Proxy
+              (HTTP proxy tunnel)     (e.g., nghttpx -s)
+
+Benchmarking tool
+-----------------
+
+The ``h2load`` program is a benchmarking tool for HTTP/2 and SPDY.
+The SPDY support is enabled if the program was built with spdylay
+library.  The UI of ``h2load`` is heavily inspired by ``weighttp``
+(https://github.com/lighttpd/weighttp).  The typical usage is as
+follows::
+
+    $ h2load -n100000 -c100 -m100 https://localhost:8443/
+    starting benchmark...
+    spawning thread #0: 100 concurrent clients, 100000 total requests
+    Protocol: TLSv1.2
+    Cipher: ECDHE-RSA-AES128-GCM-SHA256
+    progress: 10% done
+    progress: 20% done
+    progress: 30% done
+    progress: 40% done
+    progress: 50% done
+    progress: 60% done
+    progress: 70% done
+    progress: 80% done
+    progress: 90% done
+    progress: 100% done
+
+    finished in 7.10s, 14092 req/s, 55.67MB/s
+    requests: 100000 total, 100000 started, 100000 done, 100000 succeeded, 0 failed, 0 errored
+    status codes: 100000 2xx, 0 3xx, 0 4xx, 0 5xx
+    traffic: 414200800 bytes total, 2723100 bytes headers, 409600000 bytes data
+                        min         max         mean         sd        +/- sd
+    time for request:   283.86ms       1.46s    659.70ms    150.87ms    84.68%
+
+The above example issued total 100000 requests, using 100 concurrent
+clients (in other words, 100 HTTP/2 sessions), and maximum 100 streams
+per client.  With ``-t`` option, ``h2load`` will use multiple native
+threads to avoid saturating single core on client side.
+
+.. warning::
+
+   **Don't use this tool against publicly available servers.** That is
+   considered a DOS attack.  Please only use against your private
+   servers.
+
+HPACK tools
+-----------
+
+The ``src`` directory contains HPACK tools.  The ``deflatehd`` is a
+command-line header compression tool.  The ``inflatehd`` is
+command-line header decompression tool.  Both tools read input from
+stdin and write output to stdout.  The errors are written to stderr.
+They take JSON as input and output.  We use (mostly) same JSON data
+format described at https://github.com/http2jp/hpack-test-case
+
+deflatehd - header compressor
++++++++++++++++++++++++++++++
+
+The ``deflatehd`` reads JSON data or HTTP/1-style header fields from
+stdin and outputs compressed header block in JSON.
+
+For the JSON input, the root JSON object must include ``cases`` key.
+Its value has to include the sequence of input header set.  They share
+the same compression context and are processed in the order they
+appear.  Each item in the sequence is a JSON object and it must
+include ``headers`` key.  Its value is an array of a JSON object,
+which includes exactly one name/value pair.
+
+Example:
+
+.. code-block:: json
+
+    {
+      "cases":
+      [
+        {
+          "headers": [
+            { ":method": "GET" },
+            { ":path": "/" }
+          ]
+        },
+        {
+          "headers": [
+            { ":method": "POST" },
+            { ":path": "/" }
+          ]
+        }
+      ]
+    }
+
+
+With ``-t`` option, the program can accept more familiar HTTP/1 style
+header field block.  Each header set is delimited by empty line:
+
+Example::
+
+    :method: GET
+    :scheme: https
+    :path: /
+
+    :method: POST
+    user-agent: nghttp2
+
+The output is JSON object.  It should include ``cases`` key and its
+value is an array of JSON object, which has at least following keys:
+
+seq
+    The index of header set in the input.
+
+input_length
+    The sum of length of name/value pair in the input.
+
+output_length
+    The length of compressed header block.
+
+percentage_of_original_size
+    ``input_length`` / ``output_length`` * 100
+
+wire
+    The compressed header block in hex string.
+
+headers
+    The input header set.
+
+header_table_size
+    The header table size adjusted before deflating header set.
+
+Examples:
+
+.. code-block:: json
+
+    {
+      "cases":
+      [
+        {
+          "seq": 0,
+          "input_length": 66,
+          "output_length": 20,
+          "percentage_of_original_size": 30.303030303030305,
+          "wire": "01881f3468e5891afcbf83868a3d856659c62e3f",
+          "headers": [
+            {
+              ":authority": "example.org"
+            },
+            {
+              ":method": "GET"
+            },
+            {
+              ":path": "/"
+            },
+            {
+              ":scheme": "https"
+            },
+            {
+              "user-agent": "nghttp2"
+            }
+          ],
+          "header_table_size": 4096
+        }
+        ,
+        {
+          "seq": 1,
+          "input_length": 74,
+          "output_length": 10,
+          "percentage_of_original_size": 13.513513513513514,
+          "wire": "88448504252dd5918485",
+          "headers": [
+            {
+              ":authority": "example.org"
+            },
+            {
+              ":method": "POST"
+            },
+            {
+              ":path": "/account"
+            },
+            {
+              ":scheme": "https"
+            },
+            {
+              "user-agent": "nghttp2"
+            }
+          ],
+          "header_table_size": 4096
+        }
+      ]
+    }
+
+
+The output can be used as the input for ``inflatehd`` and
+``deflatehd``.
+
+With ``-d`` option, the extra ``header_table`` key is added and its
+associated value includes the state of dynamic header table after the
+corresponding header set was processed.  The value includes at least
+the following keys:
+
+entries
+    The entry in the header table.  If ``referenced`` is ``true``, it
+    is in the reference set.  The ``size`` includes the overhead (32
+    bytes).  The ``index`` corresponds to the index of header table.
+    The ``name`` is the header field name and the ``value`` is the
+    header field value.
+
+size
+    The sum of the spaces entries occupied, this includes the
+    entry overhead.
+
+max_size
+    The maximum header table size.
+
+deflate_size
+    The sum of the spaces entries occupied within
+    ``max_deflate_size``.
+
+max_deflate_size
+    The maximum header table size encoder uses.  This can be smaller
+    than ``max_size``.  In this case, encoder only uses up to first
+    ``max_deflate_size`` buffer.  Since the header table size is still
+    ``max_size``, the encoder has to keep track of entries ouside the
+    ``max_deflate_size`` but inside the ``max_size`` and make sure
+    that they are no longer referenced.
+
+Example:
+
+.. code-block:: json
+
+    {
+      "cases":
+      [
+        {
+          "seq": 0,
+          "input_length": 66,
+          "output_length": 20,
+          "percentage_of_original_size": 30.303030303030305,
+          "wire": "01881f3468e5891afcbf83868a3d856659c62e3f",
+          "headers": [
+            {
+              ":authority": "example.org"
+            },
+            {
+              ":method": "GET"
+            },
+            {
+              ":path": "/"
+            },
+            {
+              ":scheme": "https"
+            },
+            {
+              "user-agent": "nghttp2"
+            }
+          ],
+          "header_table_size": 4096,
+          "header_table": {
+            "entries": [
+              {
+                "index": 1,
+                "name": "user-agent",
+                "value": "nghttp2",
+                "referenced": true,
+                "size": 49
+              },
+              {
+                "index": 2,
+                "name": ":scheme",
+                "value": "https",
+                "referenced": true,
+                "size": 44
+              },
+              {
+                "index": 3,
+                "name": ":path",
+                "value": "/",
+                "referenced": true,
+                "size": 38
+              },
+              {
+                "index": 4,
+                "name": ":method",
+                "value": "GET",
+                "referenced": true,
+                "size": 42
+              },
+              {
+                "index": 5,
+                "name": ":authority",
+                "value": "example.org",
+                "referenced": true,
+                "size": 53
+              }
+            ],
+            "size": 226,
+            "max_size": 4096,
+            "deflate_size": 226,
+            "max_deflate_size": 4096
+          }
+        }
+        ,
+        {
+          "seq": 1,
+          "input_length": 74,
+          "output_length": 10,
+          "percentage_of_original_size": 13.513513513513514,
+          "wire": "88448504252dd5918485",
+          "headers": [
+            {
+              ":authority": "example.org"
+            },
+            {
+              ":method": "POST"
+            },
+            {
+              ":path": "/account"
+            },
+            {
+              ":scheme": "https"
+            },
+            {
+              "user-agent": "nghttp2"
+            }
+          ],
+          "header_table_size": 4096,
+          "header_table": {
+            "entries": [
+              {
+                "index": 1,
+                "name": ":method",
+                "value": "POST",
+                "referenced": true,
+                "size": 43
+              },
+              {
+                "index": 2,
+                "name": "user-agent",
+                "value": "nghttp2",
+                "referenced": true,
+                "size": 49
+              },
+              {
+                "index": 3,
+                "name": ":scheme",
+                "value": "https",
+                "referenced": true,
+                "size": 44
+              },
+              {
+                "index": 4,
+                "name": ":path",
+                "value": "/",
+                "referenced": false,
+                "size": 38
+              },
+              {
+                "index": 5,
+                "name": ":method",
+                "value": "GET",
+                "referenced": false,
+                "size": 42
+              },
+              {
+                "index": 6,
+                "name": ":authority",
+                "value": "example.org",
+                "referenced": true,
+                "size": 53
+              }
+            ],
+            "size": 269,
+            "max_size": 4096,
+            "deflate_size": 269,
+            "max_deflate_size": 4096
+          }
+        }
+      ]
+    }
+
+inflatehd - header decompressor
++++++++++++++++++++++++++++++++
+
+The ``inflatehd`` reads JSON data from stdin and outputs decompressed
+name/value pairs in JSON.
+
+The root JSON object must include ``cases`` key.  Its value has to
+include the sequence of compressed header block.  They share the same
+compression context and are processed in the order they appear.  Each
+item in the sequence is a JSON object and it must have at least
+``wire`` key.  Its value is a compressed header block in hex string.
+
+Example:
+
+.. code-block:: json
+
+    {
+      "cases":
+      [
+        { "wire": "8285" },
+        { "wire": "8583" }
+      ]
+    }
+
+The output is JSON object.  It should include ``cases`` key and its
+value is an array of JSON object, which has at least following keys:
+
+seq
+    The index of header set in the input.
+
+headers
+    The JSON array includes decompressed name/value pairs.
+
+wire
+    The compressed header block in hex string.
+
+header_table_size
+    The header table size adjusted before inflating compressed header
+    block.
+
+Example:
+
+.. code-block:: json
+
+    {
+      "cases":
+      [
+        {
+          "seq": 0,
+          "wire": "01881f3468e5891afcbf83868a3d856659c62e3f",
+          "headers": [
+            {
+              ":authority": "example.org"
+            },
+            {
+              ":method": "GET"
+            },
+            {
+              ":path": "/"
+            },
+            {
+              ":scheme": "https"
+            },
+            {
+              "user-agent": "nghttp2"
+            }
+          ],
+          "header_table_size": 4096
+        }
+        ,
+        {
+          "seq": 1,
+          "wire": "88448504252dd5918485",
+          "headers": [
+            {
+              ":method": "POST"
+            },
+            {
+              ":path": "/account"
+            },
+            {
+              "user-agent": "nghttp2"
+            },
+            {
+              ":scheme": "https"
+            },
+            {
+              ":authority": "example.org"
+            }
+          ],
+          "header_table_size": 4096
+        }
+      ]
+    }
+
+The output can be used as the input for ``deflatehd`` and
+``inflatehd``.
+
+With ``-d`` option, the extra ``header_table`` key is added and its
+associated value includes the state of dynamic header table after the
+corresponding header set was processed.  The format is the same as
+``deflatehd``.
+
+libnghttp2_asio: High level HTTP/2 C++ library
+----------------------------------------------
+
+libnghttp2_asio is C++ library built on top of libnghttp2 and provides
+high level abstraction API to build HTTP/2 applications.  It depends
+on Boost::ASIO library and OpenSSL.  Currently libnghttp2_asio
+provides server side API.
+
+libnghttp2_asio is not built by default.  Use ``--enable-asio-lib``
+configure flag to build libnghttp2_asio.  The required Boost libraries
+are:
+
+* Boost::Asio
+* Boost::System
+* Boost::Thread
+
+Server API is designed to build HTTP/2 server very easily to utilize
+C++11 anonymous function and closure.  The bare minimum example of
+HTTP/2 server looks like this:
+
+.. code-block:: cpp
+
+    #include <nghttp2/asio_http2.h>
+
+    using namespace nghttp2::asio_http2;
+    using namespace nghttp2::asio_http2::server;
+
+    int main(int argc, char *argv[]) {
+      http2 server;
+
+      server.listen("*", 3000, [](const std::shared_ptr<request> &req,
+                                  const std::shared_ptr<response> &res) {
+        res->write_head(200);
+        res->end("hello, world");
+      });
+    }
+
+For more details, see the documentation of libnghttp2_asio.
+
+Python bindings
+---------------
+
+This ``python`` directory contains nghttp2 Python bindings.  The
+bindings currently provide HPACK compressor and decompressor classes
+and HTTP/2 server.
+
+The extension module is called ``nghttp2``.
+
+``make`` will build the bindings and target Python version is
+determined by configure script.  If the detected Python version is not
+what you expect, specify a path to Python executable in ``PYTHON``
+variable as an argument to configure script (e.g., ``./configure
+PYTHON=/usr/bin/python3.4``).
+
+The following example code illustrates basic usage of HPACK compressor
+and decompressor in Python:
+
+.. code-block:: python
+
+    import binascii
+    import nghttp2
+
+    deflater = nghttp2.HDDeflater()
+    inflater = nghttp2.HDInflater()
+
+    data = deflater.deflate([(b'foo', b'bar'),
+                             (b'baz', b'buz')])
+    print(binascii.b2a_hex(data))
+
+    hdrs = inflater.inflate(data)
+    print(hdrs)
+
+The ``nghttp2.HTTP2Server`` class builds on top of the asyncio event
+loop.  On construction, *RequestHandlerClass* must be given, which
+must be a subclass of ``nghttp2.BaseRequestHandler`` class.
+
+The ``BaseRequestHandler`` class is used to handle the HTTP/2 stream.
+By default, it does nothing.  It must be subclassed to handle each
+event callback method.
+
+The first callback method invoked is ``on_headers()``.  It is called
+when HEADERS frame, which includes request header fields, has arrived.
+
+If request has request body, ``on_data(data)`` is invoked for each
+chunk of received data.
+
+When whole request is received, ``on_request_done()`` is invoked.
+
+When stream is closed, ``on_close(error_code)`` is called.
+
+The application can send response using ``send_response()`` method.
+It can be used in ``on_headers()``, ``on_data()`` or
+``on_request_done()``.
+
+The application can push resource using ``push()`` method.  It must be
+used before ``send_response()`` call.
+
+The following instance variables are available:
+
+client_address
+    Contains a tuple of the form (host, port) referring to the
+    client's address.
+
+stream_id
+    Stream ID of this stream.
+
+scheme
+    Scheme of the request URI.  This is a value of :scheme header
+    field.
+
+method
+    Method of this stream.  This is a value of :method header field.
+
+host
+    This is a value of :authority or host header field.
+
+path
+    This is a value of :path header field.
+
+The following example illustrates the HTTP2Server and
+BaseRequestHandler usage:
+
+.. code-block:: python
+
+    #!/usr/bin/env python
+
+    import io, ssl
+    import nghttp2
+
+    class Handler(nghttp2.BaseRequestHandler):
+
+        def on_headers(self):
+            self.push(path='/css/bootstrap.css',
+                      request_headers = [('content-length', '3')],
+                      status=200,
+                      body='foo')
+
+            self.push(path='/js/bootstrap.js',
+                      method='GET',
+                      request_headers = [('content-length', '10')],
+                      status=200,
+                      body='foobarbuzz')
+
+            self.send_response(status=200,
+                               headers = [('content-type', 'text/plain')],
+                               body=io.BytesIO(b'nghttp2-python FTW'))
+
+    ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+    ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2
+    ctx.load_cert_chain('server.crt', 'server.key')
+
+    # give None to ssl to make the server non-SSL/TLS
+    server = nghttp2.HTTP2Server(('127.0.0.1', 8443), Handler, ssl=ctx)
+    server.serve_forever()
+
+Contribution
+------------
+
+[This text was composed based on 1.2. License section of curl/libcurl
+project.]
+
+When contributing with code, you agree to put your changes and new
+code under the same license nghttp2 is already using unless stated and
+agreed otherwise.
+
+When changing existing source code, you do not alter the copyright of
+the original file(s).  The copyright will still be owned by the
+original creator(s) or those who have been assigned copyright by the
+original author(s).
+
+By submitting a patch to the nghttp2 project, you are assumed to have
+the right to the code and to be allowed by your employer or whatever
+to hand over that patch/code to us.  We will credit you for your
+changes as far as possible, to give credit but also to keep a trace
+back to who made what changes.  Please always provide us with your
+full real name when contributing!
+
+See `Contribution Guidelines
+<https://nghttp2.org/documentation/contribute.html>`_ for more
+details.
diff --git a/android-config b/android-config
new file mode 100755 (executable)
index 0000000..c31ae1a
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# nghttp2 - HTTP/2 C Library
+#
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if [ -z "$ANDROID_HOME" ]; then
+    echo 'No $ANDROID_HOME specified.'
+    exit 1
+fi
+PREFIX=$ANDROID_HOME/usr/local
+TOOLCHAIN=$ANDROID_HOME/toolchain
+PATH=$TOOLCHAIN/bin:$PATH
+
+./configure \
+    --disable-shared \
+    --host=arm-linux-androideabi \
+    --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+    --with-xml-prefix="$PREFIX" \
+    --without-libxml2 \
+    --disable-python-bindings \
+    --disable-examples \
+    CC=clang \
+    CXX=clang++ \
+    CPPFLAGS="-I$PREFIX/include" \
+    PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
+    LDFLAGS="-L$PREFIX/lib"
diff --git a/android-make b/android-make
new file mode 100755 (executable)
index 0000000..375045a
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# nghttp2 - HTTP/2 C Library
+#
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if [ -z "$ANDROID_HOME" ]; then
+    echo 'No $ANDROID_HOME specified.'
+    exit 1
+fi
+TOOLCHAIN=$ANDROID_HOME/toolchain
+PATH=$TOOLCHAIN/bin:$PATH
+
+make "$@"
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..84f37bf
--- /dev/null
@@ -0,0 +1,727 @@
+dnl nghttp2 - HTTP/2 C Library
+
+dnl Copyright (c) 2012, 2013, 2014, 2015 Tatsuhiro Tsujikawa
+
+dnl Permission is hereby granted, free of charge, to any person obtaining
+dnl a copy of this software and associated documentation files (the
+dnl "Software"), to deal in the Software without restriction, including
+dnl without limitation the rights to use, copy, modify, merge, publish,
+dnl distribute, sublicense, and/or sell copies of the Software, and to
+dnl permit persons to whom the Software is furnished to do so, subject to
+dnl the following conditions:
+
+dnl The above copyright notice and this permission notice shall be
+dnl included in all copies or substantial portions of the Software.
+
+dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+dnl EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+dnl MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+dnl NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+dnl LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+dnl OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+dnl WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+dnl Do not change user variables!
+dnl http://www.gnu.org/software/automake/manual/html_node/Flag-Variables-Ordering.html
+
+AC_PREREQ(2.61)
+AC_INIT([nghttp2], [0.7.4-DEV], [t-tujikawa@users.sourceforge.net])
+LT_PREREQ([2.2.6])
+LT_INIT()
+dnl See versioning rule:
+dnl  http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+AC_SUBST(LT_CURRENT, 9)
+AC_SUBST(LT_REVISION, 0)
+AC_SUBST(LT_AGE, 4)
+
+major=`echo $PACKAGE_VERSION |cut -d. -f1 | sed -e "s/[^0-9]//g"`
+minor=`echo $PACKAGE_VERSION |cut -d. -f2 | sed -e "s/[^0-9]//g"`
+patch=`echo $PACKAGE_VERSION |cut -d. -f3 | cut -d- -f1 | sed -e "s/[^0-9]//g"`
+
+PACKAGE_VERSION_NUM=`printf "0x%02x%02x%02x" "$major" "$minor" "$patch"`
+
+AC_SUBST(PACKAGE_VERSION_NUM)
+
+AC_CANONICAL_BUILD
+AC_CANONICAL_HOST
+AC_CANONICAL_TARGET
+
+AC_CONFIG_MACRO_DIR([m4])
+
+AM_INIT_AUTOMAKE([subdir-objects])
+# comment out for now since this requires automake 1.13 or higher and
+# travis has older one.
+# AM_EXTRA_RECURSIVE_TARGETS([it])
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+AC_CONFIG_HEADERS([config.h])
+
+dnl Checks for command-line options
+AC_ARG_ENABLE([werror],
+    [AS_HELP_STRING([--enable-werror],
+                    [Turn on compile time warnings])],
+    [werror=$enableval], [werror=no])
+
+AC_ARG_ENABLE([debug],
+    [AS_HELP_STRING([--enable-debug],
+                    [Turn on debug output])],
+    [debug=$enableval], [debug=no])
+
+AC_ARG_ENABLE([threads],
+    [AS_HELP_STRING([--disable-threads],
+                    [Turn off threading in apps])],
+    [threads=$enableval], [threads=yes])
+
+AC_ARG_ENABLE([app],
+    [AS_HELP_STRING([--enable-app],
+                    [Build applications (nghttp, nghttpd and nghttpx) [default=check]])],
+    [request_app=$enableval], [request_app=check])
+
+AC_ARG_ENABLE([hpack-tools],
+    [AS_HELP_STRING([--enable-hpack-tools],
+                    [Build HPACK tools [default=check]])],
+    [request_hpack_tools=$enableval], [request_hpack_tools=check])
+
+AC_ARG_ENABLE([asio-lib],
+    [AS_HELP_STRING([--enable-asio-lib],
+                    [Build C++ libnghttp2_asio library [default=no]])],
+    [request_asio_lib=$enableval], [request_asio_lib=no])
+
+AC_ARG_ENABLE([examples],
+    [AS_HELP_STRING([--enable-examples],
+                    [Build examples [default=check]])],
+    [request_examples=$enableval], [request_examples=check])
+
+AC_ARG_ENABLE([python-bindings],
+    [AS_HELP_STRING([--enable-python-bindings],
+                    [Build Python bindings [default=check]])],
+    [request_python_bindings=$enableval], [request_python_bindings=check])
+
+AC_ARG_ENABLE([failmalloc],
+    [AS_HELP_STRING([--disable-failmalloc],
+                    [Do not build failmalloc test program])],
+    [request_failmalloc=$enableval], [request_failmalloc=yes])
+
+AC_ARG_WITH([libxml2],
+    [AS_HELP_STRING([--with-libxml2],
+                    [Use libxml2 [default=check]])],
+    [request_libxml2=$withval], [request_libxml2=check])
+
+AC_ARG_WITH([jemalloc],
+    [AS_HELP_STRING([--with-jemalloc],
+                    [Use jemalloc [default=check]])],
+    [request_jemalloc=$withval], [request_jemalloc=check])
+
+AC_ARG_WITH([spdylay],
+    [AS_HELP_STRING([--with-spdylay],
+                    [Use spdylay [default=check]])],
+    [request_spdylay=$withval], [request_spdylay=check])
+
+AC_ARG_WITH([cython],
+    [AS_HELP_STRING([--with-cython=PATH],
+                    [Use cython in given PATH])],
+    [cython_path=$withval], [])
+
+dnl Define variables
+AC_ARG_VAR([CYTHON], [the Cython executable])
+
+dnl Checks for programs
+AC_PROG_CC
+AC_PROG_CXX
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+AM_PROG_CC_C_O
+PKG_PROG_PKG_CONFIG([0.20])
+
+if [test "x$request_python_bindings" != "xno"]; then
+  AM_PATH_PYTHON([2.7],, [:])
+  AX_PYTHON_DEVEL([>= '2.7'])
+fi
+
+if test "x${cython_path}" = "x"; then
+  AC_CHECK_PROGS([CYTHON], [cython.py cython])
+else
+  CYTHON=${cython_path}
+  AC_SUBST([CYTHON])
+fi
+
+#
+# If we're running GCC or clang define _U_ to be "__attribute__((unused))"
+# so we can use _U_ to flag unused function parameters and not get warnings
+# about them. Otherwise, define _U_ to be an empty string so that _U_ used
+# to flag an unused function parameters will compile with other compilers.
+#
+# XXX - similar hints for other compilers?
+#
+if test "x$GCC" = "xyes" -o "x$CC" = "xclang" ; then
+  AC_DEFINE([_U_], [__attribute__((unused))], [Hint to the compiler that a function parameters is not used])
+else
+  AC_DEFINE([_U_], , [Hint to the compiler that a function parameters is not used])
+fi
+
+AX_CXX_COMPILE_STDCXX_11([noext], [optional])
+
+AC_LANG_PUSH(C++)
+
+# Check that std::chrono::steady_clock is available. In particular,
+# gcc 4.6 does not have one, but has monotonic_clock which is the old
+# name existed in the pre-standard draft. If steady_clock is not
+# available, don't define HAVE_STEADY_CLOCK and replace steady_clock
+# with monotonic_clock.
+AC_MSG_CHECKING([whether std::chrono::steady_clock is available])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <chrono>
+]],
+[[
+auto tp = std::chrono::steady_clock::now();
+]])],
+    [AC_DEFINE([HAVE_STEADY_CLOCK], [1],
+               [Define to 1 if you have the `std::chrono::steady_clock`.])
+     AC_MSG_RESULT([yes])],
+    [AC_MSG_RESULT([no])])
+
+# Check that std::future is available.
+AC_MSG_CHECKING([whether std::future is available])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <vector>
+#include <future>
+]],
+[[
+std::vector<std::future<int>> v;
+]])],
+    [AC_DEFINE([HAVE_STD_FUTURE], [1],
+               [Define to 1 if you have the `std::future`.])
+     have_std_future=yes
+     AC_MSG_RESULT([yes])],
+    [have_std_future=no
+     AC_MSG_RESULT([no])])
+
+# Check that thread_local is available.
+AC_MSG_CHECKING([whether thread_local is available])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+]],
+[[
+thread_local int n;
+]])],
+    [AC_DEFINE([HAVE_THREAD_LOCAL], [1],
+               [Define to 1 if you have the `thread_local`.])
+     have_thread_local=yes
+     AC_MSG_RESULT([yes])],
+    [have_thread_local=no
+     AC_MSG_RESULT([no])])
+
+# Check that std::map::emplace is available for g++-4.7.
+AC_MSG_CHECKING([whether std::map::emplace is available])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <map>
+]],
+[[
+std::map<int, int>().emplace(1, 2);
+]])],
+    [AC_DEFINE([HAVE_STD_MAP_EMPLACE], [1],
+               [Define to 1 if you have the `std::map::emplace`.])
+     have_std_map_emplace=yes
+     AC_MSG_RESULT([yes])],
+    [have_std_map_emplace=no
+     AC_MSG_RESULT([no])])
+
+AC_LANG_POP()
+
+# Checks for libraries.
+
+# Additional libraries required for tests.
+TESTLDADD=
+
+# Additional libraries required for programs under src directory.
+APPLDFLAGS=
+
+LIBS_OLD=$LIBS
+# Search for dlsym function, which is used in tests. Linux needs -ldl,
+# but netbsd does not need it.
+AC_SEARCH_LIBS([dlsym], [dl])
+TESTLDADD="$LIBS $TESTLDADD"
+LIBS=$LIBS_OLD
+
+LIBS_OLD=$LIBS
+AC_SEARCH_LIBS([clock_gettime], [rt],
+               [AC_DEFINE([HAVE_CLOCK_GETTIME], [1],
+                          [Define to 1 if you have the `clock_gettime`.])])
+APPLDFLAGS="$LIBS $APPLDFLAGS"
+LIBS=$LIBS_OLD
+
+case "$host" in
+  *android*)
+    android_build=yes
+    # android does not need -pthread, but needs followng 3 libs for C++
+    APPLDFLAGS="$APPLDFLAGS -lstdc++ -latomic -lsupc++"
+    ;;
+  *)
+    PTHREAD_LDFLAGS="-pthread"
+    APPLDFLAGS="$APPLDFLAGS $PTHREAD_LDFLAGS"
+    ;;
+esac
+
+# zlib
+if test "x$android_build" = "xyes"; then
+  # Use zlib provided by NDK
+  APPLDFLAGS="-lz $APPLDFLAGS"
+  have_zlib=yes
+else
+  PKG_CHECK_MODULES([ZLIB], [zlib >= 1.2.3], [have_zlib=yes], [have_zlib=no])
+
+  if test "x${have_zlib}" = "xno"; then
+    AC_MSG_NOTICE($ZLIB_PKG_ERRORS)
+  fi
+fi
+
+# cunit
+PKG_CHECK_MODULES([CUNIT], [cunit >= 2.1], [have_cunit=yes], [have_cunit=no])
+# If pkg-config does not find cunit, check it using AC_CHECK_LIB.  We
+# do this because Debian (Ubuntu) lacks pkg-config file for cunit.
+if test "x${have_cunit}" = "xno"; then
+  AC_MSG_WARN([${CUNIT_PKG_ERRORS}])
+  AC_CHECK_LIB([cunit], [CU_initialize_registry],
+               [have_cunit=yes], [have_cunit=no])
+  if test "x${have_cunit}" = "xyes"; then
+    CUNIT_LIBS="-lcunit"
+    CUNIT_CFLAGS=""
+    AC_SUBST([CUNIT_LIBS])
+    AC_SUBST([CUNIT_CFLAGS])
+  fi
+fi
+if test "x${have_cunit}" = "xyes"; then
+  # cunit in Mac OS X requires ncurses. Note that in Mac OS X, test
+  # program can be built without -lncurses, but it emits runtime
+  # error.
+  case "${build}" in
+    *-apple-darwin*)
+      CUNIT_LIBS="$CUNIT_LIBS -lncurses"
+      AC_SUBST([CUNIT_LIBS])
+      ;;
+  esac
+fi
+
+AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
+
+# libev (for src)
+# libev does not have pkg-config file.  Check it in an old way.
+LIBS_OLD=$LIBS
+# android requires -lm for floor
+AC_CHECK_LIB([ev], [ev_time], [have_libev=yes], [have_libev=no], [-lm])
+if test "x${have_libev}" = "xyes"; then
+  AC_CHECK_HEADER([ev.h], [have_libev=yes], [have_libev=no])
+  if test "x${have_libev}" = "xyes"; then
+    LIBEV_LIBS=-lev
+    LIBEV_CFLAGS=
+    AC_SUBST([LIBEV_LIBS])
+    AC_SUBST([LIBEV_CFLAGS])
+  fi
+fi
+LIBS=$LIBS_OLD
+
+# openssl (for src)
+PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.1],
+                  [have_openssl=yes], [have_openssl=no])
+if test "x${have_openssl}" = "xno"; then
+  AC_MSG_NOTICE($OPENSSL_PKG_ERRORS)
+fi
+
+# libevent_openssl (for examples)
+# 2.0.8 is required because we use evconnlistener_set_error_cb()
+PKG_CHECK_MODULES([LIBEVENT_OPENSSL], [libevent_openssl >= 2.0.8],
+                  [have_libevent_openssl=yes], [have_libevent_openssl=no])
+if test "x${have_libevent_openssl}" = "xno"; then
+  AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS)
+fi
+
+# jansson (for src/nghttp, src/deflatehd and src/inflatehd)
+PKG_CHECK_MODULES([JANSSON], [jansson >= 2.5],
+                  [have_jansson=yes], [have_jansson=no])
+if test "x${have_jansson}" == "xyes"; then
+  AC_DEFINE([HAVE_JANSSON], [1],
+            [Define to 1 if you have `libjansson` library.])
+else
+  AC_MSG_NOTICE($JANSSON_PKG_ERRORS)
+fi
+
+# libxml2 (for src/nghttp)
+have_libxml2=no
+if test "x${request_libxml2}" != "xno"; then
+  AM_PATH_XML2(2.7.7, [have_libxml2=yes], [have_libxml2=no])
+  if test "x${have_libxml2}" = "xyes"; then
+    AC_DEFINE([HAVE_LIBXML2], [1], [Define to 1 if you have `libxml2` library.])
+  fi
+fi
+
+if test "x${request_libxml2}" = "xyes" &&
+   test "x${have_libxml2}" != "xyes"; then
+  AC_MSG_ERROR([libxml2 was requested (--with-libxml2) but not found])
+fi
+
+AM_CONDITIONAL([HAVE_LIBXML2], [ test "x${have_libxml2}" = "xyes" ])
+
+# jemalloc
+have_jemalloc=no
+if test "x${request_jemalloc}" != "xno"; then
+  LIBS_OLD=$LIBS
+  AC_SEARCH_LIBS([malloc_stats_print], [jemalloc], [have_jemalloc=yes], [],
+                 [$PTHREAD_LDFLAGS])
+  LIBS=$LIBS_OLD
+  if test "x${have_jemalloc}" = "xyes" &&
+     test "x${ac_cv_search_malloc_stats_print}" != "xnone required"; then
+    JEMALLOC_LIBS=${ac_cv_search_malloc_stats_print}
+    AC_SUBST([JEMALLOC_LIBS])
+  fi
+fi
+
+if test "x${request_jemalloc}" = "xyes" &&
+   test "x${have_jemalloc}" != "xyes"; then
+  AC_MSG_ERROR([jemalloc was requested (--with-jemalloc) but not found])
+fi
+
+# spdylay (for src/nghttpx and src/h2load)
+have_spdylay=no
+if test "x${request_spdylay}" != "xno"; then
+  PKG_CHECK_MODULES([LIBSPDYLAY], [libspdylay >= 1.3.0],
+                    [have_spdylay=yes], [have_spdylay=no])
+  if test "x${have_spdylay}" = "xyes"; then
+    AC_DEFINE([HAVE_SPDYLAY], [1], [Define to 1 if you have `spdylay` library.])
+  else
+    AC_MSG_NOTICE($LIBSPDYLAY_PKG_ERRORS)
+    AC_MSG_NOTICE([The SPDY support in nghttpx and h2load will be disabled.])
+  fi
+fi
+
+if test "x${request_spdylay}" = "xyes" &&
+   test "x${have_spdylay}" != "xyes"; then
+  AC_MSG_ERROR([spdylay was requested (--with-spdylay) but not found])
+fi
+
+AM_CONDITIONAL([HAVE_SPDYLAY], [ test "x${have_spdylay}" = "xyes" ])
+
+# Check Boost Asio library
+have_asio_lib=no
+
+if test "x${request_asio_lib}" = "xyes"; then
+  AX_BOOST_BASE([1.54.0], [have_boost_base=yes], [have_boost_base=no])
+
+  if test "x${have_boost_base}" = "xyes"; then
+    AX_BOOST_ASIO()
+    AX_BOOST_SYSTEM()
+    AX_BOOST_THREAD()
+
+    if test "x${ax_cv_boost_asio}" = "xyes" &&
+       test "x${ax_cv_boost_system}" = "xyes" &&
+       test "x${ax_cv_boost_thread}" = "xyes"; then
+      have_asio_lib=yes
+    fi
+  fi
+fi
+
+# The nghttp, nghttpd and nghttpx under src depend on zlib, OpenSSL
+# and libev
+enable_app=no
+if test "x${request_app}" != "xno" &&
+   test "x${have_zlib}" = "xyes" &&
+   test "x${have_openssl}" = "xyes" &&
+   test "x${have_libev}" = "xyes"; then
+  enable_app=yes
+fi
+
+if test "x${request_app}" = "xyes" &&
+   test "x${enable_app}" != "xyes"; then
+  AC_MSG_ERROR([applications were requested (--enable-app) but dependencies are not met.])
+fi
+
+AM_CONDITIONAL([ENABLE_APP], [ test "x${enable_app}" = "xyes" ])
+
+enable_hpack_tools=no
+# HPACK tools requires jansson
+if test "x${request_hpack_tools}" != "xno" &&
+   test "x${have_jansson}" = "xyes"; then
+  enable_hpack_tools=yes
+fi
+
+if test "x${request_hpack_tools}" = "xyes" &&
+   test "x${enable_hpack_tools}" != "xyes"; then
+  AC_MSG_ERROR([HPACK tools were requested (--enable-hpack-tools) but dependencies are not met.])
+fi
+
+AM_CONDITIONAL([ENABLE_HPACK_TOOLS], [ test "x${enable_hpack_tools}" = "xyes" ])
+
+# C++ library libnghttp2_asio
+
+enable_asio_lib=no
+if test "x${request_asio_lib}" != "xno" &&
+   test "x${have_asio_lib}" = "xyes"; then
+  enable_asio_lib=yes
+fi
+
+AM_CONDITIONAL([ENABLE_ASIO_LIB], [ test "x${enable_asio_lib}" = "xyes" ])
+
+# The example programs depend on OpenSSL and libevent_openssl
+enable_examples=no
+if test "x${request_examples}" != "xno" &&
+   test "x${have_openssl}" = "xyes" &&
+   test "x${have_libevent_openssl}" = "xyes"; then
+  enable_examples=yes
+fi
+
+if test "x${request_examples}" = "xyes" &&
+   test "x${enable_examples}" != "xyes"; then
+  AC_MSG_ERROR([examples were requested (--enable-examples) but dependencies are not met.])
+fi
+
+AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ])
+
+# Python bindings
+enable_python_bindings=no
+if test "x${request_python_bindings}" != "xno" &&
+   test "x${CYTHON}" != "x" &&
+   test "x${PYTHON}" != "x:" &&
+   test "x${have_python_dev}" = "xyes"; then
+  enable_python_bindings=yes
+fi
+
+if test "x${request_python_bindings}" = "xyes" &&
+   test "x${enable_python_bindings}" != "xyes"; then
+  AC_MSG_ERROR([python bindings were requested (--enable-python-bindings) but dependencies are not met.])
+fi
+
+AM_CONDITIONAL([ENABLE_PYTHON_BINDINGS],
+               [test "x${enable_python_bindings}" = "xyes"])
+
+# Produce cython conditional, so that we can distribute generated C
+# source
+AM_CONDITIONAL([HAVE_CYTHON], [test "x${CYTHON}" != "x"])
+
+# failmalloc tests
+enable_failmalloc=no
+if test "x${request_failmalloc}" = "xyes"; then
+  enable_failmalloc=yes
+fi
+
+AM_CONDITIONAL([ENABLE_FAILMALLOC], [ test "x${enable_failmalloc}" = "xyes" ])
+
+# Checks for header files.
+AC_HEADER_ASSERT
+AC_CHECK_HEADERS([ \
+  arpa/inet.h \
+  netinet/in.h \
+  pwd.h \
+  stddef.h \
+  stdint.h \
+  stdlib.h \
+  string.h \
+  time.h \
+  unistd.h \
+])
+
+case "${host}" in
+  *mingw*)
+    # For ntohl, ntohs in Windows
+    AC_CHECK_HEADERS([winsock2.h])
+    ;;
+esac
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_UINT8_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT64_T
+AC_CHECK_TYPES([ptrdiff_t])
+AC_C_BIGENDIAN
+AC_SYS_LARGEFILE
+
+AC_CHECK_MEMBER([struct tm.tm_gmtoff], [have_struct_tm_tm_gmtoff=yes],
+                [have_struct_tm_tm_gmtoff=no], [[#include <time.h>]])
+
+if test "x$have_struct_tm_tm_gmtoff" = "xyes"; then
+  AC_DEFINE([HAVE_STRUCT_TM_TM_GMTOFF], [1],
+            [Define to 1 if you have `struct tm.tm_gmtoff` member.])
+fi
+
+# Checks for library functions.
+if test "x$cross_compiling" != "xyes"; then
+  AC_FUNC_MALLOC
+fi
+AC_CHECK_FUNCS([ \
+  _Exit \
+  accept4 \
+  getpwnam \
+  memmove \
+  memset \
+  timegm \
+])
+
+# timerfd_create was added in linux kernel 2.6.25
+
+AC_CHECK_FUNC([timerfd_create],
+              [have_timerfd_create=yes], [have_timerfd_create=no])
+
+
+# Checks for epoll availability, primarily for examples/tiny-nghttpd
+AX_HAVE_EPOLL([have_epoll=yes], [have_epoll=no])
+
+AM_CONDITIONAL([ENABLE_TINY_NGHTTPD],
+               [ test "x${have_epoll}" = "xyes" &&
+                 test "x${have_timerfd_create}" = "xyes"])
+
+dnl Windows library for winsock2
+case "${host}" in
+  *mingw*)
+    LIBS="$LIBS -lws2_32"
+    ;;
+esac
+
+ac_save_CFLAGS=$CFLAGS
+CFLAGS=
+
+if test "x$werror" != "xno"; then
+    AX_CHECK_COMPILE_FLAG([-Wall], [CFLAGS="$CFLAGS -Wall"])
+    AX_CHECK_COMPILE_FLAG([-Wextra], [CFLAGS="$CFLAGS -Wextra"])
+    AX_CHECK_COMPILE_FLAG([-Werror], [CFLAGS="$CFLAGS -Werror"])
+    AX_CHECK_COMPILE_FLAG([-Wmissing-prototypes], [CFLAGS="$CFLAGS -Wmissing-prototypes"])
+    AX_CHECK_COMPILE_FLAG([-Wstrict-prototypes], [CFLAGS="$CFLAGS -Wstrict-prototypes"])
+    AX_CHECK_COMPILE_FLAG([-Wmissing-declarations], [CFLAGS="$CFLAGS -Wmissing-declarations"])
+    AX_CHECK_COMPILE_FLAG([-Wpointer-arith], [CFLAGS="$CFLAGS -Wpointer-arith"])
+    AX_CHECK_COMPILE_FLAG([-Wdeclaration-after-statement], [CFLAGS="$CFLAGS -Wdeclaration-after-statement"])
+    AX_CHECK_COMPILE_FLAG([-Wformat-security], [CFLAGS="$CFLAGS -Wformat-security"])
+    AX_CHECK_COMPILE_FLAG([-Wwrite-strings], [CFLAGS="$CFLAGS -Wwrite-strings"])
+    AX_CHECK_COMPILE_FLAG([-Wshadow], [CFLAGS="$CFLAGS -Wshadow"])
+    AX_CHECK_COMPILE_FLAG([-Winline], [CFLAGS="$CFLAGS -Winline"])
+    AX_CHECK_COMPILE_FLAG([-Wnested-externs], [CFLAGS="$CFLAGS -Wnested-externs"])
+    AX_CHECK_COMPILE_FLAG([-Wfloat-equal], [CFLAGS="$CFLAGS -Wfloat-equal"])
+    AX_CHECK_COMPILE_FLAG([-Wundef], [CFLAGS="$CFLAGS -Wundef"])
+    AX_CHECK_COMPILE_FLAG([-Wendif-labels], [CFLAGS="$CFLAGS -Wendif-labels"])
+    AX_CHECK_COMPILE_FLAG([-Wempty-body], [CFLAGS="$CFLAGS -Wempty-body"])
+    AX_CHECK_COMPILE_FLAG([-Wcast-align], [CFLAGS="$CFLAGS -Wcast-align"])
+    AX_CHECK_COMPILE_FLAG([-Wclobbered], [CFLAGS="$CFLAGS -Wclobbered"])
+    AX_CHECK_COMPILE_FLAG([-Wvla], [CFLAGS="$CFLAGS -Wvla"])
+    AX_CHECK_COMPILE_FLAG([-Wpragmas], [CFLAGS="$CFLAGS -Wpragmas"])
+    AX_CHECK_COMPILE_FLAG([-Wunreachable-code], [CFLAGS="$CFLAGS -Wunreachable-code"])
+    AX_CHECK_COMPILE_FLAG([-Waddress], [CFLAGS="$CFLAGS -Waddress"])
+    AX_CHECK_COMPILE_FLAG([-Wattributes], [CFLAGS="$CFLAGS -Wattributes"])
+    AX_CHECK_COMPILE_FLAG([-Wdiv-by-zero], [CFLAGS="$CFLAGS -Wdiv-by-zero"])
+    AX_CHECK_COMPILE_FLAG([-Wshorten-64-to-32], [CFLAGS="$CFLAGS -Wshorten-64-to-32"])
+
+    # Only work with Clang for the moment
+    AX_CHECK_COMPILE_FLAG([-Wheader-guard], [CFLAGS="$CFLAGS -Wheader-guard"])
+fi
+
+WARNCFLAGS=$CFLAGS
+CFLAGS=$ac_save_CFLAGS
+
+AC_SUBST([WARNCFLAGS])
+
+if test "x$debug" != "xno"; then
+    AC_DEFINE([DEBUGBUILD], [1], [Define to 1 to enable debug output.])
+fi
+
+enable_threads=yes
+# Some platform does not have working std::future or thread_local.  We
+# disable threading for those platforms.
+if test "x$threads" != "xyes" ||
+   test "x$have_std_future" != "xyes" ||
+   test "x$have_thread_local" != "xyes"; then
+    enable_threads=no
+    AC_DEFINE([NOTHREADS], [1], [Define to 1 if you want to disable threads.])
+fi
+
+AC_SUBST([TESTLDADD])
+AC_SUBST([APPLDFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  lib/Makefile
+  lib/libnghttp2.pc
+  lib/includes/Makefile
+  lib/includes/nghttp2/nghttp2ver.h
+  tests/Makefile
+  tests/testdata/Makefile
+  third-party/Makefile
+  src/Makefile
+  src/includes/Makefile
+  src/libnghttp2_asio.pc
+  examples/Makefile
+  python/Makefile
+  python/setup.py
+  integration-tests/Makefile
+  integration-tests/config.go
+  integration-tests/setenv
+  doc/Makefile
+  doc/conf.py
+  doc/index.rst
+  doc/package_README.rst
+  doc/tutorial-client.rst
+  doc/tutorial-server.rst
+  doc/tutorial-hpack.rst
+  doc/nghttpx-howto.rst
+  doc/h2load-howto.rst
+  doc/libnghttp2_asio.rst
+  doc/python-apiref.rst
+  doc/building-android-binary.rst
+  doc/nghttp2.h.rst
+  doc/nghttp2ver.h.rst
+  doc/asio_http2.h.rst
+  doc/contribute.rst
+  contrib/Makefile
+])
+AC_OUTPUT
+
+AC_MSG_NOTICE([summary of build options:
+
+    Version:        ${VERSION} shared $LT_CURRENT:$LT_REVISION:$LT_AGE
+    Host type:      ${host}
+    Install prefix: ${prefix}
+    C compiler:     ${CC}
+    CFLAGS:         ${CFLAGS}
+    WARNCFLAGS:     ${WARNCFLAGS}
+    LDFLAGS:        ${LDFLAGS}
+    LIBS:           ${LIBS}
+    CPPFLAGS:       ${CPPFLAGS}
+    C preprocessor: ${CPP}
+    C++ compiler:   ${CXX}
+    CXXFLAGS:       ${CXXFLAGS}
+    CXXCPP:         ${CXXCPP}
+    Library types:  Shared=${enable_shared}, Static=${enable_static}
+    Python:
+      Python:         ${PYTHON}
+      PYTHON_VERSION: ${PYTHON_VERSION}
+      pyexecdir:      ${pyexecdir}
+      Python-dev:     ${have_python_dev}
+      PYTHON_CPPFLAGS:${PYTHON_CPPFLAGS}
+      PYTHON_LDFLAGS: ${PYTHON_LDFLAGS}
+      Cython:         ${CYTHON}
+    Test:
+      CUnit:          ${have_cunit}
+      Failmalloc:     ${enable_failmalloc}
+    Libs:
+      OpenSSL:        ${have_openssl}
+      Libxml2:        ${have_libxml2}
+      Libev:          ${have_libev}
+      Libevent(SSL):  ${have_libevent_openssl}
+      Spdylay:        ${have_spdylay}
+      Jansson:        ${have_jansson}
+      Jemalloc:       ${have_jemalloc}
+      Boost CPPFLAGS: ${BOOST_CPPFLAGS}
+      Boost LDFLAGS:  ${BOOST_LDFLAGS}
+      Boost::ASIO:    ${BOOST_ASIO_LIB}
+      Boost::System:  ${BOOST_SYSTEM_LIB}
+      Boost::Thread:  ${BOOST_THREAD_LIB}
+    Features:
+      Applications:   ${enable_app}
+      HPACK tools:    ${enable_hpack_tools}
+      Libnghttp2_asio:${enable_asio_lib}
+      Examples:       ${enable_examples}
+      Python bindings:${enable_python_bindings}
+      Threading:      ${enable_threads}
+])
diff --git a/contrib/.gitignore b/contrib/.gitignore
new file mode 100644 (file)
index 0000000..a08baf9
--- /dev/null
@@ -0,0 +1 @@
+nghttpx-init
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644 (file)
index 0000000..6727a84
--- /dev/null
@@ -0,0 +1,39 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2014 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+EXTRA_DIST = nghttpx-init.in nghttpx-logrotate
+
+edit = sed -e 's|@bindir[@]|$(bindir)|g'
+
+nghttpx-init: Makefile
+       rm -f $@ $@.tmp
+       $(edit) $(srcdir)/$@.in > $@.tmp
+       chmod +x $@.tmp
+       mv $@.tmp $@
+
+nghttpx-init: $(srcdir)/nghttpx-init.in
+
+all-local: nghttpx-init
+
+clean-local:
+       -rm -f nghttpx-init nghttpx-init.tmp
diff --git a/contrib/nghttpx-init.in b/contrib/nghttpx-init.in
new file mode 100644 (file)
index 0000000..a1620a9
--- /dev/null
@@ -0,0 +1,173 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          nghttpx
+# Required-Start:    $remote_fs $syslog
+# Required-Stop:     $remote_fs $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: nghttpx initscript
+# Description:       nghttpx initscript
+### END INIT INFO
+
+# Author: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
+#
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
+DESC="HTTP/2 reverse proxy"
+NAME=nghttpx
+# Depending on the configuration, binary may be located under @sbindir@
+DAEMON=@bindir@/$NAME
+PIDFILE=/var/run/$NAME.pid
+DAEMON_ARGS="--conf /etc/nghttpx/nghttpx.conf --pid-file=$PIDFILE"
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
+# and status_of_proc is working.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+       start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+               $DAEMON_ARGS \
+               || return 2
+       # Add code here, if necessary, that waits for the process to be ready
+       # to handle requests from services started subsequently which depend
+       # on this one.  As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
+       RETVAL="$?"
+       [ "$RETVAL" = 2 ] && return 2
+
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       #start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
+       #[ "$?" = 2 ] && return 2
+       # Many daemons don't delete their pidfiles when they exit.
+       rm -f $PIDFILE
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  upgrade)
+        log_daemon_msg "Upgrade $DESC" "$NAME"
+        pid=`pidofproc -p $PIDFILE $NAME`
+        case "$?" in
+                0) echo "Sending USR2 signal to $pid"
+                   kill -USR2 $pid
+                   echo "Waiting for new binary..."
+                   sleep 5
+                   echo "Sending QUIT signal to $pid"
+                   kill -QUIT $pid
+                   log_end_msg 0
+                   ;;
+                *) echo "pidofproc() failed"
+                   log_end_msg 1
+                   ;;
+        esac
+        ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload|upgrade}" >&2
+       exit 3
+       ;;
+esac
+
+:
diff --git a/contrib/nghttpx-logrotate b/contrib/nghttpx-logrotate
new file mode 100644 (file)
index 0000000..56b37be
--- /dev/null
@@ -0,0 +1,18 @@
+/var/log/nghttpx/*.log {
+        weekly
+        missingok
+        rotate 52
+        compress
+        delaycompress
+        notifempty
+        create 0640 www-data adm
+        sharedscripts
+        prerotate
+                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
+                        run-parts /etc/logrotate.d/httpd-prerotate; \
+                fi \
+        endscript
+        postrotate
+                [ -s /run/nghttpx.pid ] && kill -USR1 `cat /run/nghttpx.pid`
+        endscript
+}
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..65f5f83
--- /dev/null
@@ -0,0 +1,3 @@
+apiref.rst
+conf.py
+manual
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644 (file)
index 0000000..a9d3404
--- /dev/null
@@ -0,0 +1,198 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+man_MANS = nghttp.1 nghttpd.1 nghttpx.1 h2load.1
+
+EXTRA_DIST = \
+       mkapiref.py \
+       README.rst \
+       apiref-header.rst \
+       nghttp.1.rst \
+       nghttpd.1.rst \
+       nghttpx.1.rst \
+       h2load.1.rst \
+       sources/index.rst \
+       sources/tutorial-client.rst \
+       sources/tutorial-server.rst \
+       sources/tutorial-hpack.rst \
+       sources/nghttpx-howto.rst \
+       sources/h2load-howto.rst \
+       sources/libnghttp2_asio.rst \
+       sources/python-apiref.rst \
+       sources/building-android-binary.rst \
+       sources/contribute.rst \
+       _themes/sphinx_rtd_theme/footer.html \
+       _themes/sphinx_rtd_theme/theme.conf \
+       _themes/sphinx_rtd_theme/layout_old.html \
+       _themes/sphinx_rtd_theme/__init__.py \
+       _themes/sphinx_rtd_theme/layout.html \
+       _themes/sphinx_rtd_theme/search.html \
+       _themes/sphinx_rtd_theme/breadcrumbs.html \
+       _themes/sphinx_rtd_theme/versions.html \
+       _themes/sphinx_rtd_theme/searchbox.html \
+       _themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf \
+       _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg \
+       _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff \
+       _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot \
+       _themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf \
+       _themes/sphinx_rtd_theme/static/js/theme.js \
+       _themes/sphinx_rtd_theme/static/css/theme.css \
+       _themes/sphinx_rtd_theme/static/css/badge_only.css \
+       $(man_MANS)
+
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = manual
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html       to make standalone HTML files"
+       @echo "  dirhtml    to make HTML files named index.html in directories"
+       @echo "  singlehtml to make a single large HTML file"
+       @echo "  pickle     to make pickle files"
+       @echo "  json       to make JSON files"
+       @echo "  htmlhelp   to make HTML files and a HTML help project"
+       @echo "  qthelp     to make HTML files and a qthelp project"
+       @echo "  devhelp    to make HTML files and a Devhelp project"
+       @echo "  epub       to make an epub"
+       @echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+       @echo "  text       to make text files"
+       @echo "  man        to make manual pages"
+       @echo "  changes    to make an overview of all changed/added/deprecated items"
+       @echo "  linkcheck  to check all external links for integrity"
+       @echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+apiref.rst: $(top_builddir)/lib/includes/nghttp2/nghttp2ver.h \
+       $(top_builddir)/lib/includes/nghttp2/nghttp2.h
+       $(PYTHON) $(top_srcdir)/doc/mkapiref.py \
+       --header $(top_srcdir)/doc/apiref-header.rst $^ > $@
+
+clean-local:
+       -rm apiref.rst
+       -rm -rf $(BUILDDIR)/*
+
+html: apiref.rst
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+       $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+       @echo
+       @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+       @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/nghttp2.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/nghttp2.qhc"
+
+devhelp:
+       $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+       @echo
+       @echo "Build finished."
+       @echo "To view the help file:"
+       @echo "# mkdir -p $$HOME/.local/share/devhelp/nghttp2"
+       @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/nghttp2"
+       @echo "# devhelp"
+
+epub:
+       $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+       @echo
+       @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+       @echo "Run \`make' in that directory to run these through (pdf)latex" \
+             "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through pdflatex..."
+       $(MAKE) -C $(BUILDDIR)/latex all-pdf
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+       $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+       @echo
+       @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man: apiref.rst
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+       @echo
+       @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+       @echo
+       @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/README.rst b/doc/README.rst
new file mode 100644 (file)
index 0000000..35bfe7e
--- /dev/null
@@ -0,0 +1,160 @@
+nghttp2 Documentation
+=====================
+
+The documentation of nghttp2 is generated using Sphinx.  This
+directory contains the source files to be processed by Sphinx.  The
+source file for API reference is generated using a script called
+``mkapiref.py`` from the nghttp2 C source code.
+
+Generating API reference
+------------------------
+
+As described earlier, we use ``mkapiref.py`` to generate rst formatted
+text of API reference from C source code.  The ``mkapiref.py`` is not
+so flexible and it requires that C source code is formatted in rather
+strict rules.
+
+To generate API reference, just run ``make html``. It runs
+``mkapiref.py`` and then run Sphinx to build the entire document.
+
+The ``mkapiref.py`` reads C source code and searches the comment block
+starts with ``/**``. In other words, it only processes the comment
+block starting ``/**``. The comment block must end with ``*/``. The
+``mkapiref.py`` requires that which type of the object this comment
+block refers to.  To specify the type of the object, the next line
+must contain the so-caled action keyword.  Currently, the following
+action keywords are supported: ``@function``, ``@functypedef``,
+``@enum``, ``@struct`` and ``@union``. The following sections
+describes each action keyword.
+
+@function
+#########
+
+``@function`` is used to refer to the function.  The comment block is
+used for the document for the function.  After the script sees the end
+of the comment block, it consumes the lines as the function
+declaration until the line which ends with ``;`` is encountered.
+
+In Sphinx doc, usually the function argument is formatted like
+``*this*``.  But in C, ``*`` is used for dereferencing a pointer and
+we must escape ``*`` with a back slash. To avoid this, we format the
+argument like ``|this|``. The ``mkapiref.py`` translates it with
+``*this*``, as escaping ``*`` inside ``|`` and ``|`` as necessary.
+Note that this shadows the substitution feature of Sphinx.
+
+The example follows::
+
+    /**
+     * @function
+     *
+     * Submits PING frame to the |session|.
+     */
+    int nghttp2_submit_ping(nghttp2_session *session);
+
+
+@functypedef
+############
+
+``@functypedef`` is used to refer to the typedef of the function
+pointer. The formatting rule is pretty much the same with
+``@function``, but this outputs ``type`` domain, rather than
+``function`` domain.
+
+The example follows::
+
+    /**
+     * @functypedef
+     *
+     * Callback function invoked when |session| wants to send data to
+     * remote peer.
+     */
+    typedef ssize_t (*nghttp2_send_callback)
+    (nghttp2_session *session,
+     const uint8_t *data, size_t length, int flags, void *user_data);
+
+@enum
+#####
+
+``@enum`` is used to refer to the enum.  Currently, only enum typedefs
+are supported.  The comment block is used for the document for the
+enum type itself. To document each values, put comment block starting
+with the line ``/**`` and ending with the ``*/`` just before the enum
+value.  When the line starts with ``}`` is encountered, the
+``mkapiref.py`` extracts strings next to ``}`` as the name of enum.
+
+At the time of this writing, Sphinx does not support enum type. So we
+use ``type`` domain for enum it self and ``macro`` domain for each
+value. To refer to the enum value, use ``:enum:`` pseudo role. The
+``mkapiref.py`` replaces it with ``:macro:``. By doing this, when
+Sphinx will support enum officially, we can replace ``:enum:`` with
+the official role easily.
+
+The example follows::
+
+    /**
+     * @enum
+     * Error codes used in the nghttp2 library.
+     */
+    typedef enum {
+      /**
+       * Invalid argument passed.
+       */
+      NGHTTP2_ERR_INVALID_ARGUMENT = -501,
+      /**
+       * Zlib error.
+       */
+      NGHTTP2_ERR_ZLIB = -502,
+    } nghttp2_error;
+
+@struct
+#######
+
+``@struct`` is used to refer to the struct. Currently, only struct
+typedefs are supported. The comment block is used for the document for
+the struct type itself.To document each member, put comment block
+starting with the line ``/**`` and ending with the ``*/`` just before
+the member.  When the line starts with ``}`` is encountered, the
+``mkapiref.py`` extracts strings next to ``}`` as the name of struct.
+The block-less typedef is also supported. In this case, typedef
+declaration must be all in one line and the ``mkapiref.py`` uses last
+word as the name of struct.
+
+Some examples follow::
+    
+    /**
+     * @struct
+     * The control frame header.
+     */
+    typedef struct {
+      /**
+       * SPDY protocol version.
+       */
+      uint16_t version;
+      /**
+       * The type of this control frame.
+       */
+      uint16_t type;
+      /**
+       * The control frame flags.
+       */
+      uint8_t flags;
+      /**
+       * The length field of this control frame.
+       */
+      int32_t length;
+    } nghttp2_ctrl_hd;
+        
+    /**
+     * @struct
+     *
+     * The primary structure to hold the resources needed for a SPDY
+     * session. The details of this structure is hidden from the public
+     * API.
+     */
+    typedef struct nghttp2_session nghttp2_session;
+
+@union
+######
+
+``@union`` is used to refer to the union. Currently, ``@union`` is an
+alias of ``@struct``.
diff --git a/doc/_themes/sphinx_rtd_theme/__init__.py b/doc/_themes/sphinx_rtd_theme/__init__.py
new file mode 100644 (file)
index 0000000..1440863
--- /dev/null
@@ -0,0 +1,17 @@
+"""Sphinx ReadTheDocs theme.
+
+From https://github.com/ryan-roemer/sphinx-bootstrap-theme.
+
+"""
+import os
+
+VERSION = (0, 1, 5)
+
+__version__ = ".".join(str(v) for v in VERSION)
+__version_full__ = __version__
+
+
+def get_html_theme_path():
+    """Return list of HTML theme paths."""
+    cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+    return cur_dir
diff --git a/doc/_themes/sphinx_rtd_theme/breadcrumbs.html b/doc/_themes/sphinx_rtd_theme/breadcrumbs.html
new file mode 100644 (file)
index 0000000..ff0938e
--- /dev/null
@@ -0,0 +1,19 @@
+<div role="navigation" aria-label="breadcrumbs navigation">
+  <ul class="wy-breadcrumbs">
+    <li><a href="{{ pathto(master_doc) }}">Docs</a> &raquo;</li>
+      {% for doc in parents %}
+          <li><a href="{{ doc.link|e }}">{{ doc.title }}</a> &raquo;</li>
+      {% endfor %}
+    <li>{{ title }}</li>
+      <li class="wy-breadcrumbs-aside">
+        {% if display_github %}
+          <a href="https://github.com/{{ github_user }}/{{ github_repo }}/blob/{{ github_version }}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-github"> Edit on GitHub</a>
+        {% elif display_bitbucket %}
+          <a href="https://bitbucket.org/{{ bitbucket_user }}/{{ bitbucket_repo }}/src/{{ bitbucket_version}}{{ conf_py_path }}{{ pagename }}{{ source_suffix }}" class="fa fa-bitbucket"> Edit on Bitbucket</a>
+        {% elif show_source and has_source and sourcename %}
+          <a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow"> View page source</a>
+        {% endif %}
+      </li>
+  </ul>
+  <hr/>
+</div>
diff --git a/doc/_themes/sphinx_rtd_theme/footer.html b/doc/_themes/sphinx_rtd_theme/footer.html
new file mode 100644 (file)
index 0000000..94f6dc3
--- /dev/null
@@ -0,0 +1,33 @@
+<footer>
+  {% if next or prev %}
+    <div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
+      {% if next %}
+        <a href="{{ next.link|e }}" class="btn btn-neutral float-right" title="{{ next.title|striptags|e }}">Next <span class="fa fa-arrow-circle-right"></span></a>
+      {% endif %}
+      {% if prev %}
+        <a href="{{ prev.link|e }}" class="btn btn-neutral" title="{{ prev.title|striptags|e }}"><span class="fa fa-arrow-circle-left"></span> Previous</a>
+      {% endif %}
+    </div>
+  {% endif %}
+
+  <hr/>
+
+  <div role="contentinfo">
+    <p>
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+      {%- endif %}
+    {%- endif %}
+
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
+    {%- endif %}
+    </p>
+  </div>
+
+  {% trans %}Built with <a href="http://sphinx-doc.org/">Sphinx</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>{% endtrans %}.
+  
+</footer>
diff --git a/doc/_themes/sphinx_rtd_theme/layout.html b/doc/_themes/sphinx_rtd_theme/layout.html
new file mode 100644 (file)
index 0000000..5f9ef21
--- /dev/null
@@ -0,0 +1,164 @@
+{# TEMPLATE VAR SETTINGS #}
+{%- set url_root = pathto('', 1) %}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+  {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
+{%- else %}
+  {%- set titlesuffix = "" %}
+{%- endif %}
+
+<!DOCTYPE html>
+<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  {% block htmltitle %}
+  <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
+  {% endblock %}
+
+  {# FAVICON #}
+  {% if favicon %}
+    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
+  {% endif %}
+
+  {# CSS #}
+  <link href='https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic|Roboto+Slab:400,700|Inconsolata:400,700&subset=latin,cyrillic' rel='stylesheet' type='text/css'>
+
+  {# OPENSEARCH #}
+  {% if not embedded %}
+    {% if use_opensearch %}
+      <link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" href="{{ pathto('_static/opensearch.xml', 1) }}"/>
+    {% endif %}
+
+  {% endif %}
+
+  {# RTD hosts this file, so just load on non RTD builds #}
+  {% if not READTHEDOCS %}
+    <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
+  {% endif %}
+
+  {% for cssfile in css_files %}
+    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+  {% endfor %}
+
+  {%- block linktags %}
+    {%- if hasdoc('about') %}
+        <link rel="author" title="{{ _('About these documents') }}"
+              href="{{ pathto('about') }}"/>
+    {%- endif %}
+    {%- if hasdoc('genindex') %}
+        <link rel="index" title="{{ _('Index') }}"
+              href="{{ pathto('genindex') }}"/>
+    {%- endif %}
+    {%- if hasdoc('search') %}
+        <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}"/>
+    {%- endif %}
+    {%- if hasdoc('copyright') %}
+        <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}"/>
+    {%- endif %}
+    <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}"/>
+    {%- if parents %}
+        <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}"/>
+    {%- endif %}
+    {%- if next %}
+        <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}"/>
+    {%- endif %}
+    {%- if prev %}
+        <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}"/>
+    {%- endif %}
+  {%- endblock %}
+  {%- block extrahead %} {% endblock %}
+
+  {# Keep modernizr in head - http://modernizr.com/docs/#installing #}
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
+
+</head>
+
+<body class="wy-body-for-nav" role="document">
+
+  <div class="wy-grid-for-nav">
+
+    {# SIDE NAV, TOGGLES ON MOBILE #}
+    <nav data-toggle="wy-nav-shift" class="wy-nav-side">
+      <div class="wy-side-nav-search">
+        {% block sidebartitle %}
+          <a href="{{ pathto(master_doc) }}" class="fa fa-home"> {{ project }}</a>
+        {% endblock %}
+        {% include "searchbox.html" %}
+      </div>
+
+      <div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
+        {% block menu %}
+          {% set toctree = toctree(maxdepth=2, collapse=False, includehidden=True) %}
+          {% if toctree %}
+              {{ toctree }}
+          {% else %}
+              <!-- Local TOC -->
+              <div class="local-toc">{{ toc }}</div>
+          {% endif %}
+        {% endblock %}
+      </div>
+      &nbsp;
+    </nav>
+
+    <section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
+
+      {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #}
+      <nav class="wy-nav-top" role="navigation" aria-label="top navigation">
+        <i data-toggle="wy-nav-top" class="fa fa-bars"></i>
+        <a href="{{ pathto(master_doc) }}">{{ project }}</a>
+      </nav>
+
+
+      {# PAGE CONTENT #}
+      <div class="wy-nav-content">
+        <div class="rst-content">
+          {% include "breadcrumbs.html" %}
+          <div role="main" class="document">
+            {% block body %}{% endblock %}
+          </div>
+          {% include "footer.html" %}
+        </div>
+      </div>
+
+    </section>
+
+  </div>
+  {% include "versions.html" %}
+
+  {% if not embedded %}
+
+    <script type="text/javascript">
+        var DOCUMENTATION_OPTIONS = {
+            URL_ROOT:'{{ url_root }}',
+            VERSION:'{{ release|e }}',
+            COLLAPSE_INDEX:false,
+            FILE_SUFFIX:'{{ '' if no_search_suffix else file_suffix }}',
+            HAS_SOURCE:  {{ has_source|lower }}
+        };
+    </script>
+    {%- for scriptfile in script_files %}
+      <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
+    {%- endfor %}
+
+  {% endif %}
+
+  {# RTD hosts this file, so just load on non RTD builds #}
+  {% if not READTHEDOCS %}
+    <script type="text/javascript" src="{{ pathto('_static/js/theme.js', 1) }}"></script>
+  {% endif %}
+
+  {# STICKY NAVIGATION #}
+  {% if theme_sticky_navigation %}
+  <script type="text/javascript">
+      jQuery(function () {
+          SphinxRtdTheme.StickyNav.enable();
+      });
+  </script>
+  {% endif %}
+
+  {%- block footer %} {% endblock %}
+
+</body>
+</html>
diff --git a/doc/_themes/sphinx_rtd_theme/layout_old.html b/doc/_themes/sphinx_rtd_theme/layout_old.html
new file mode 100644 (file)
index 0000000..deb8df2
--- /dev/null
@@ -0,0 +1,205 @@
+{#
+    basic/layout.html
+    ~~~~~~~~~~~~~~~~~
+
+    Master layout template for Sphinx themes.
+
+    :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- block doctype -%}
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+{%- endblock %}
+{%- set reldelim1 = reldelim1 is not defined and ' &raquo;' or reldelim1 %}
+{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
+{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
+                         (sidebars != []) %}
+{%- set url_root = pathto('', 1) %}
+{# XXX necessary? #}
+{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
+{%- if not embedded and docstitle %}
+  {%- set titlesuffix = " &mdash; "|safe + docstitle|e %}
+{%- else %}
+  {%- set titlesuffix = "" %}
+{%- endif %}
+
+{%- macro relbar() %}
+    <div class="related">
+      <h3>{{ _('Navigation') }}</h3>
+      <ul>
+        {%- for rellink in rellinks %}
+        <li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
+          <a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
+             {{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
+          {%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
+        {%- endfor %}
+        {%- block rootrellink %}
+        <li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
+        {%- endblock %}
+        {%- for parent in parents %}
+          <li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
+        {%- endfor %}
+        {%- block relbaritems %} {% endblock %}
+      </ul>
+    </div>
+{%- endmacro %}
+
+{%- macro sidebar() %}
+      {%- if render_sidebar %}
+      <div class="sphinxsidebar">
+        <div class="sphinxsidebarwrapper">
+          {%- block sidebarlogo %}
+          {%- if logo %}
+            <p class="logo"><a href="{{ pathto(master_doc) }}">
+              <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
+            </a></p>
+          {%- endif %}
+          {%- endblock %}
+          {%- if sidebars != None %}
+            {#- new style sidebar: explicitly include/exclude templates #}
+            {%- for sidebartemplate in sidebars %}
+            {%- include sidebartemplate %}
+            {%- endfor %}
+          {%- else %}
+            {#- old style sidebars: using blocks -- should be deprecated #}
+            {%- block sidebartoc %}
+            {%- include "localtoc.html" %}
+            {%- endblock %}
+            {%- block sidebarrel %}
+            {%- include "relations.html" %}
+            {%- endblock %}
+            {%- block sidebarsourcelink %}
+            {%- include "sourcelink.html" %}
+            {%- endblock %}
+            {%- if customsidebar %}
+            {%- include customsidebar %}
+            {%- endif %}
+            {%- block sidebarsearch %}
+            {%- include "searchbox.html" %}
+            {%- endblock %}
+          {%- endif %}
+        </div>
+      </div>
+      {%- endif %}
+{%- endmacro %}
+
+{%- macro script() %}
+    <script type="text/javascript">
+      var DOCUMENTATION_OPTIONS = {
+        URL_ROOT:    '{{ url_root }}',
+        VERSION:     '{{ release|e }}',
+        COLLAPSE_INDEX: false,
+        FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
+        HAS_SOURCE:  {{ has_source|lower }}
+      };
+    </script>
+    {%- for scriptfile in script_files %}
+    <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
+    {%- endfor %}
+{%- endmacro %}
+
+{%- macro css() %}
+    <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
+    <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
+    {%- for cssfile in css_files %}
+    <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
+    {%- endfor %}
+{%- endmacro %}
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
+    {{ metatags }}
+    {%- block htmltitle %}
+    <title>{{ title|striptags|e }}{{ titlesuffix }}</title>
+    {%- endblock %}
+    {{ css() }}
+    {%- if not embedded %}
+    {{ script() }}
+    {%- if use_opensearch %}
+    <link rel="search" type="application/opensearchdescription+xml"
+          title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
+          href="{{ pathto('_static/opensearch.xml', 1) }}"/>
+    {%- endif %}
+    {%- if favicon %}
+    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
+    {%- endif %}
+    {%- endif %}
+{%- block linktags %}
+    {%- if hasdoc('about') %}
+    <link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
+    {%- endif %}
+    {%- if hasdoc('genindex') %}
+    <link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
+    {%- endif %}
+    {%- if hasdoc('search') %}
+    <link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
+    {%- endif %}
+    {%- if hasdoc('copyright') %}
+    <link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
+    {%- endif %}
+    <link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
+    {%- if parents %}
+    <link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
+    {%- endif %}
+    {%- if next %}
+    <link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
+    {%- endif %}
+    {%- if prev %}
+    <link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
+    {%- endif %}
+{%- endblock %}
+{%- block extrahead %} {% endblock %}
+  </head>
+  <body>
+{%- block header %}{% endblock %}
+
+{%- block relbar1 %}{{ relbar() }}{% endblock %}
+
+{%- block content %}
+  {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
+
+    <div class="document">
+  {%- block document %}
+      <div class="documentwrapper">
+      {%- if render_sidebar %}
+        <div class="bodywrapper">
+      {%- endif %}
+          <div class="body">
+            {% block body %} {% endblock %}
+          </div>
+      {%- if render_sidebar %}
+        </div>
+      {%- endif %}
+      </div>
+  {%- endblock %}
+
+  {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
+      <div class="clearer"></div>
+    </div>
+{%- endblock %}
+
+{%- block relbar2 %}{{ relbar() }}{% endblock %}
+
+{%- block footer %}
+    <div class="footer">
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+      {%- endif %}
+    {%- endif %}
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
+    {%- endif %}
+    {%- if show_sphinx %}
+      {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
+    {%- endif %}
+    </div>
+    <p>asdf asdf asdf asdf 22</p>
+{%- endblock %}
+  </body>
+</html>
+
diff --git a/doc/_themes/sphinx_rtd_theme/search.html b/doc/_themes/sphinx_rtd_theme/search.html
new file mode 100644 (file)
index 0000000..e3aa9b5
--- /dev/null
@@ -0,0 +1,50 @@
+{#
+    basic/search.html
+    ~~~~~~~~~~~~~~~~~
+
+    Template for the search page.
+
+    :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+#}
+{%- extends "layout.html" %}
+{% set title = _('Search') %}
+{% set script_files = script_files + ['_static/searchtools.js'] %}
+{% block footer %}
+  <script type="text/javascript">
+    jQuery(function() { Search.loadIndex("{{ pathto('searchindex.js', 1) }}"); });
+  </script>
+  {# this is used when loading the search index using $.ajax fails,
+     such as on Chrome for documents on localhost #}
+  <script type="text/javascript" id="searchindexloader"></script>
+  {{ super() }}
+{% endblock %}
+{% block body %}
+  <noscript>
+  <div id="fallback" class="admonition warning">
+    <p class="last">
+      {% trans %}Please activate JavaScript to enable the search
+      functionality.{% endtrans %}
+    </p>
+  </div>
+  </noscript>
+
+  {% if search_performed %}
+    <h2>{{ _('Search Results') }}</h2>
+    {% if not search_results %}
+      <p>{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}</p>
+    {% endif %}
+  {% endif %}
+  <div id="search-results">
+  {% if search_results %}
+    <ul>
+    {% for href, caption, context in search_results %}
+      <li>
+        <a href="{{ pathto(item.href) }}">{{ caption }}</a>
+        <p class="context">{{ context|e }}</p>
+      </li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+  </div>
+{% endblock %}
diff --git a/doc/_themes/sphinx_rtd_theme/searchbox.html b/doc/_themes/sphinx_rtd_theme/searchbox.html
new file mode 100644 (file)
index 0000000..35ad52c
--- /dev/null
@@ -0,0 +1,9 @@
+{%- if builder != 'singlehtml' %}
+<div role="search">
+  <form id="rtd-search-form" class="wy-form" action="{{ pathto('search') }}" method="get">
+    <input type="text" name="q" placeholder="Search docs" />
+    <input type="hidden" name="check_keywords" value="yes" />
+    <input type="hidden" name="area" value="default" />
+  </form>
+</div>
+{%- endif %}
diff --git a/doc/_themes/sphinx_rtd_theme/static/css/badge_only.css b/doc/_themes/sphinx_rtd_theme/static/css/badge_only.css
new file mode 100644 (file)
index 0000000..7e17fb1
--- /dev/null
@@ -0,0 +1,2 @@
+.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:""}.icon-book:before{content:""}.fa-caret-down:before{content:""}.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.icon-caret-up:before{content:""}.fa-caret-left:before{content:""}.icon-caret-left:before{content:""}.fa-caret-right:before{content:""}.icon-caret-right:before{content:""}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}
+/*# sourceMappingURL=badge_only.css.map */
diff --git a/doc/_themes/sphinx_rtd_theme/static/css/theme.css b/doc/_themes/sphinx_rtd_theme/static/css/theme.css
new file mode 100644 (file)
index 0000000..f57e6c2
--- /dev/null
@@ -0,0 +1,5 @@
+*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*!
+ *  Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.1.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.1.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.1.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.pull-left.icon{margin-right:.3em}.fa.pull-right,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-gear:before,.fa-cog:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-rotate-right:before,.fa-repeat:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-warning:before,.fa-exclamation-triangle:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-gears:before,.fa-cogs:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-save:before,.fa-floppy-o:before{content:""}.fa-square:before{content:""}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-unsorted:before,.fa-sort:before{content:""}.fa-sort-down:before,.fa-sort-desc:before{content:""}.fa-sort-up:before,.fa-sort-asc:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-legal:before,.fa-gavel:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-flash:before,.fa-bolt:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-paste:before,.fa-clipboard:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-unlink:before,.fa-chain-broken:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:""}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:""}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:""}.fa-euro:before,.fa-eur:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-rupee:before,.fa-inr:before{content:""}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:""}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:""}.fa-won:before,.fa-krw:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-turkish-lira:before,.fa-try:before{content:""}.fa-plus-square-o:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-institution:before,.fa-bank:before,.fa-university:before{content:""}.fa-mortar-board:before,.fa-graduation-cap:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-square:before,.fa-pied-piper:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:""}.fa-file-zip-o:before,.fa-file-archive-o:before{content:""}.fa-file-sound-o:before,.fa-file-audio-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before{content:""}.fa-ge:before,.fa-empire:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-send:before,.fa-paper-plane:before{content:""}.fa-send-o:before,.fa-paper-plane-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .icon,.nav .fa,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .icon{display:inline}.btn .fa.fa-large,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980B9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27AE60}.wy-tray-container li.wy-tray-item-info{background:#2980B9}.wy-tray-container li.wy-tray-item-warning{background:#E67E22}.wy-tray-container li.wy-tray-item-danger{background:#E74C3C}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27AE60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980B9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27AE60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#E74C3C !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#E67E22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980B9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9B59B6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980B9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980B9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#E74C3C}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{float:left;display:block;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129FEA}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#E74C3C;border:1px solid #E74C3C}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#E74C3C}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#E74C3C}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#E74C3C}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #E74C3C}.wy-control-group.wy-control-group-error textarea{border:solid 1px #E74C3C}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27AE60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#E74C3C}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#E67E22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980B9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980B9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9B59B6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#E67E22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980B9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27AE60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#E74C3C !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#E74C3C;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9B59B6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#EAF2F5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980B9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980B9;cursor:pointer;color:#fff}.wy-side-nav-search{z-index:200;background-color:#2980B9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980B9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980B9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200}.wy-nav-top{display:none;background:#2980B9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}nav.stickynav{position:fixed;top:0}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980B9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27AE60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#E74C3C;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#F1C40F;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img,.rst-content .section>a>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#F1C40F;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt{color:#000}.rst-content tt big,.rst-content tt em{font-size:100% !important;line-height:normal}.rst-content tt .xref,a .rst-content tt{font-weight:bold}.rst-content a tt{color:#2980B9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980B9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:gray}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27AE60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center}
+/*# sourceMappingURL=theme.css.map */
diff --git a/doc/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf b/doc/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf
new file mode 100644 (file)
index 0000000..8b0f54e
Binary files /dev/null and b/doc/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf differ
diff --git a/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot
new file mode 100644 (file)
index 0000000..7c79c6a
Binary files /dev/null and b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot differ
diff --git a/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg
new file mode 100644 (file)
index 0000000..45fdf33
--- /dev/null
@@ -0,0 +1,414 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="fontawesomeregular" horiz-adv-x="1536" >
+<font-face units-per-em="1792" ascent="1536" descent="-256" />
+<missing-glyph horiz-adv-x="448" />
+<glyph unicode=" "  horiz-adv-x="448" />
+<glyph unicode="&#x09;" horiz-adv-x="448" />
+<glyph unicode="&#xa0;" horiz-adv-x="448" />
+<glyph unicode="&#xa8;" horiz-adv-x="1792" />
+<glyph unicode="&#xa9;" horiz-adv-x="1792" />
+<glyph unicode="&#xae;" horiz-adv-x="1792" />
+<glyph unicode="&#xb4;" horiz-adv-x="1792" />
+<glyph unicode="&#xc6;" horiz-adv-x="1792" />
+<glyph unicode="&#x2000;" horiz-adv-x="768" />
+<glyph unicode="&#x2001;" />
+<glyph unicode="&#x2002;" horiz-adv-x="768" />
+<glyph unicode="&#x2003;" />
+<glyph unicode="&#x2004;" horiz-adv-x="512" />
+<glyph unicode="&#x2005;" horiz-adv-x="384" />
+<glyph unicode="&#x2006;" horiz-adv-x="256" />
+<glyph unicode="&#x2007;" horiz-adv-x="256" />
+<glyph unicode="&#x2008;" horiz-adv-x="192" />
+<glyph unicode="&#x2009;" horiz-adv-x="307" />
+<glyph unicode="&#x200a;" horiz-adv-x="85" />
+<glyph unicode="&#x202f;" horiz-adv-x="307" />
+<glyph unicode="&#x205f;" horiz-adv-x="384" />
+<glyph unicode="&#x2122;" horiz-adv-x="1792" />
+<glyph unicode="&#x221e;" horiz-adv-x="1792" />
+<glyph unicode="&#x2260;" horiz-adv-x="1792" />
+<glyph unicode="&#xe000;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xf000;" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
+<glyph unicode="&#xf001;" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf002;" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf003;" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf004;" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
+<glyph unicode="&#xf005;" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf006;" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf007;" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf008;" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf009;" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf00a;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00b;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00c;" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
+<glyph unicode="&#xf00d;" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
+<glyph unicode="&#xf00e;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf010;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
+<glyph unicode="&#xf011;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
+<glyph unicode="&#xf012;" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf013;" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
+<glyph unicode="&#xf014;" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf015;" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
+<glyph unicode="&#xf016;" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z " />
+<glyph unicode="&#xf017;" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf018;" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
+<glyph unicode="&#xf019;" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" />
+<glyph unicode="&#xf01a;" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01b;" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01c;" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
+<glyph unicode="&#xf01d;" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01e;" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
+<glyph unicode="&#xf021;" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf022;" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
+<glyph unicode="&#xf023;" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf024;" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf025;" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
+<glyph unicode="&#xf026;" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf027;" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
+<glyph unicode="&#xf028;" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
+<glyph unicode="&#xf029;" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
+<glyph unicode="&#xf02a;" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" />
+<glyph unicode="&#xf02b;" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02c;" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02d;" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
+<glyph unicode="&#xf02e;" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf02f;" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
+<glyph unicode="&#xf030;" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf031;" horiz-adv-x="1664" d="M725 977l-170 -450q73 -1 153.5 -2t119 -1.5t52.5 -0.5l29 2q-32 95 -92 241q-53 132 -92 211zM21 -128h-21l2 79q22 7 80 18q89 16 110 31q20 16 48 68l237 616l280 724h75h53l11 -21l205 -480q103 -242 124 -297q39 -102 96 -235q26 -58 65 -164q24 -67 65 -149 q22 -49 35 -57q22 -19 69 -23q47 -6 103 -27q6 -39 6 -57q0 -14 -1 -26q-80 0 -192 8q-93 8 -189 8q-79 0 -135 -2l-200 -11l-58 -2q0 45 4 78l131 28q56 13 68 23q12 12 12 27t-6 32l-47 114l-92 228l-450 2q-29 -65 -104 -274q-23 -64 -23 -84q0 -31 17 -43 q26 -21 103 -32q3 0 13.5 -2t30 -5t40.5 -6q1 -28 1 -58q0 -17 -2 -27q-66 0 -349 20l-48 -8q-81 -14 -167 -14z" />
+<glyph unicode="&#xf032;" horiz-adv-x="1408" d="M555 15q76 -32 140 -32q131 0 216 41t122 113q38 70 38 181q0 114 -41 180q-58 94 -141 126q-80 32 -247 32q-74 0 -101 -10v-144l-1 -173l3 -270q0 -15 12 -44zM541 761q43 -7 109 -7q175 0 264 65t89 224q0 112 -85 187q-84 75 -255 75q-52 0 -130 -13q0 -44 2 -77 q7 -122 6 -279l-1 -98q0 -43 1 -77zM0 -128l2 94q45 9 68 12q77 12 123 31q17 27 21 51q9 66 9 194l-2 497q-5 256 -9 404q-1 87 -11 109q-1 4 -12 12q-18 12 -69 15q-30 2 -114 13l-4 83l260 6l380 13l45 1q5 0 14 0.5t14 0.5q1 0 21.5 -0.5t40.5 -0.5h74q88 0 191 -27 q43 -13 96 -39q57 -29 102 -76q44 -47 65 -104t21 -122q0 -70 -32 -128t-95 -105q-26 -20 -150 -77q177 -41 267 -146q92 -106 92 -236q0 -76 -29 -161q-21 -62 -71 -117q-66 -72 -140 -108q-73 -36 -203 -60q-82 -15 -198 -11l-197 4q-84 2 -298 -11q-33 -3 -272 -11z" />
+<glyph unicode="&#xf033;" horiz-adv-x="1024" d="M0 -126l17 85q4 1 77 20q76 19 116 39q29 37 41 101l27 139l56 268l12 64q8 44 17 84.5t16 67t12.5 46.5t9 30.5t3.5 11.5l29 157l16 63l22 135l8 50v38q-41 22 -144 28q-28 2 -38 4l19 103l317 -14q39 -2 73 -2q66 0 214 9q33 2 68 4.5t36 2.5q-2 -19 -6 -38 q-7 -29 -13 -51q-55 -19 -109 -31q-64 -16 -101 -31q-12 -31 -24 -88q-9 -44 -13 -82q-44 -199 -66 -306l-61 -311l-38 -158l-43 -235l-12 -45q-2 -7 1 -27q64 -15 119 -21q36 -5 66 -10q-1 -29 -7 -58q-7 -31 -9 -41q-18 0 -23 -1q-24 -2 -42 -2q-9 0 -28 3q-19 4 -145 17 l-198 2q-41 1 -174 -11q-74 -7 -98 -9z" />
+<glyph unicode="&#xf034;" horiz-adv-x="1792" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l215 -1h293l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -42.5 2t-103.5 -1t-111 -1 q-34 0 -67 -5q-10 -97 -8 -136l1 -152v-332l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-88 0 -233 -14q-48 -4 -70 -4q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q8 192 6 433l-5 428q-1 62 -0.5 118.5t0.5 102.5t-2 57t-6 15q-6 5 -14 6q-38 6 -148 6q-43 0 -100 -13.5t-73 -24.5q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1744 128q33 0 42 -18.5t-11 -44.5 l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80z" />
+<glyph unicode="&#xf035;" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l446 -1h318l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -58.5 2t-138.5 -1t-128 -1 q-94 0 -127 -5q-10 -97 -8 -136l1 -152v52l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-82 0 -233 -13q-45 -5 -70 -5q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q6 137 6 433l-5 44q0 265 -2 278q-2 11 -6 15q-6 5 -14 6q-38 6 -148 6q-50 0 -168.5 -14t-132.5 -24q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1505 113q26 -20 26 -49t-26 -49l-162 -126 q-26 -20 -44.5 -11t-18.5 42v80h-1024v-80q0 -33 -18.5 -42t-44.5 11l-162 126q-26 20 -26 49t26 49l162 126q26 20 44.5 11t18.5 -42v-80h1024v80q0 33 18.5 42t44.5 -11z" />
+<glyph unicode="&#xf036;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf037;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf038;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf039;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf03a;" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03b;" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03c;" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03d;" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" />
+<glyph unicode="&#xf03e;" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf040;" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
+<glyph unicode="&#xf041;" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
+<glyph unicode="&#xf042;" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf043;" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
+<glyph unicode="&#xf044;" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
+<glyph unicode="&#xf045;" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf046;" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
+<glyph unicode="&#xf047;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf048;" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
+<glyph unicode="&#xf049;" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
+<glyph unicode="&#xf04a;" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
+<glyph unicode="&#xf04b;" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
+<glyph unicode="&#xf04c;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04d;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04e;" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf050;" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf051;" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
+<glyph unicode="&#xf052;" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
+<glyph unicode="&#xf053;" horiz-adv-x="1152" d="M742 -37l-652 651q-37 37 -37 90.5t37 90.5l652 651q37 37 90.5 37t90.5 -37l75 -75q37 -37 37 -90.5t-37 -90.5l-486 -486l486 -485q37 -38 37 -91t-37 -90l-75 -75q-37 -37 -90.5 -37t-90.5 37z" />
+<glyph unicode="&#xf054;" horiz-adv-x="1152" d="M1099 704q0 -52 -37 -91l-652 -651q-37 -37 -90 -37t-90 37l-76 75q-37 39 -37 91q0 53 37 90l486 486l-486 485q-37 39 -37 91q0 53 37 90l76 75q36 38 90 38t90 -38l652 -651q37 -37 37 -90z" />
+<glyph unicode="&#xf055;" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf056;" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
+<glyph unicode="&#xf057;" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf058;" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf059;" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05a;" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05b;" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf05c;" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05d;" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05e;" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" />
+<glyph unicode="&#xf060;" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
+<glyph unicode="&#xf061;" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
+<glyph unicode="&#xf062;" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
+<glyph unicode="&#xf063;" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="&#xf064;" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
+<glyph unicode="&#xf065;" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf066;" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
+<glyph unicode="&#xf067;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf068;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf069;" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
+<glyph unicode="&#xf06a;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
+<glyph unicode="&#xf06b;" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf06c;" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
+<glyph unicode="&#xf06d;" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
+<glyph unicode="&#xf06e;" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
+<glyph unicode="&#xf070;" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
+<glyph unicode="&#xf071;" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
+<glyph unicode="&#xf072;" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" />
+<glyph unicode="&#xf073;" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf074;" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf075;" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf076;" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf077;" horiz-adv-x="1664" d="M1611 320q0 -53 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-486 485l-486 -485q-36 -38 -90 -38t-90 38l-75 75q-38 36 -38 90q0 53 38 91l651 651q37 37 90 37q52 0 91 -37l650 -651q38 -38 38 -91z" />
+<glyph unicode="&#xf078;" horiz-adv-x="1664" d="M1611 832q0 -53 -37 -90l-651 -651q-38 -38 -91 -38q-54 0 -90 38l-651 651q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l486 -486l486 486q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="&#xf079;" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
+<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf07b;" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07c;" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07d;" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf07e;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf080;" horiz-adv-x="1920" d="M512 512v-384h-256v384h256zM896 1024v-896h-256v896h256zM1280 768v-640h-256v640h256zM1664 1152v-1024h-256v1024h256zM1792 32v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5z M1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf081;" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf082;" d="M1307 618l23 219h-198v109q0 49 15.5 68.5t71.5 19.5h110v219h-175q-152 0 -218 -72t-66 -213v-131h-131v-219h131v-635h262v635h175zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf083;" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf084;" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
+<glyph unicode="&#xf085;" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
+<glyph unicode="&#xf086;" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
+<glyph unicode="&#xf087;" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf088;" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
+<glyph unicode="&#xf089;" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
+<glyph unicode="&#xf08a;" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
+<glyph unicode="&#xf08b;" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
+<glyph unicode="&#xf08c;" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf08d;" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
+<glyph unicode="&#xf08e;" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf090;" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf091;" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf092;" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf093;" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
+<glyph unicode="&#xf094;" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
+<glyph unicode="&#xf095;" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
+<glyph unicode="&#xf096;" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf097;" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf098;" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf099;" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
+<glyph unicode="&#xf09a;" horiz-adv-x="768" d="M511 980h257l-30 -284h-227v-824h-341v824h-170v284h170v171q0 182 86 275.5t283 93.5h227v-284h-142q-39 0 -62.5 -6.5t-34 -23.5t-13.5 -34.5t-3 -49.5v-142z" />
+<glyph unicode="&#xf09b;" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf09c;" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="&#xf09d;" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
+<glyph unicode="&#xf09e;" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
+<glyph unicode="&#xf0a0;" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
+<glyph unicode="&#xf0a1;" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
+<glyph unicode="&#xf0a2;" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM1664 128q0 -52 -38 -90t-90 -38 h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
+<glyph unicode="&#xf0a3;" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
+<glyph unicode="&#xf0a4;" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf0a5;" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf0a6;" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
+<glyph unicode="&#xf0a7;" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
+<glyph unicode="&#xf0a8;" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0a9;" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0aa;" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ab;" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ac;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" />
+<glyph unicode="&#xf0ad;" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
+<glyph unicode="&#xf0ae;" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0b0;" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
+<glyph unicode="&#xf0b1;" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0b2;" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
+<glyph unicode="&#xf0c0;" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
+<glyph unicode="&#xf0c1;" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
+<glyph unicode="&#xf0c2;" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
+<glyph unicode="&#xf0c3;" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
+<glyph unicode="&#xf0c4;" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
+<glyph unicode="&#xf0c5;" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
+<glyph unicode="&#xf0c6;" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
+<glyph unicode="&#xf0c7;" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0c8;" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0c9;" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0ca;" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cb;" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cc;" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
+<glyph unicode="&#xf0cd;" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
+<glyph unicode="&#xf0ce;" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
+<glyph unicode="&#xf0d0;" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
+<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d2;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0d3;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf0d4;" d="M678 -57q0 -38 -10 -71h-380q-95 0 -171.5 56.5t-103.5 147.5q24 45 69 77.5t100 49.5t107 24t107 7q32 0 49 -2q6 -4 30.5 -21t33 -23t31 -23t32 -25.5t27.5 -25.5t26.5 -29.5t21 -30.5t17.5 -34.5t9.5 -36t4.5 -40.5zM385 294q-234 -7 -385 -85v433q103 -118 273 -118 q32 0 70 5q-21 -61 -21 -86q0 -67 63 -149zM558 805q0 -100 -43.5 -160.5t-140.5 -60.5q-51 0 -97 26t-78 67.5t-56 93.5t-35.5 104t-11.5 99q0 96 51.5 165t144.5 69q66 0 119 -41t84 -104t47 -130t16 -128zM1536 896v-736q0 -119 -84.5 -203.5t-203.5 -84.5h-468 q39 73 39 157q0 66 -22 122.5t-55.5 93t-72 71t-72 59.5t-55.5 54.5t-22 59.5q0 36 23 68t56 61.5t65.5 64.5t55.5 93t23 131t-26.5 145.5t-75.5 118.5q-6 6 -14 11t-12.5 7.5t-10 9.5t-10.5 17h135l135 64h-437q-138 0 -244.5 -38.5t-182.5 -133.5q0 126 81 213t207 87h960 q119 0 203.5 -84.5t84.5 -203.5v-96h-256v256h-128v-256h-256v-128h256v-256h128v256h256z" />
+<glyph unicode="&#xf0d5;" horiz-adv-x="1664" d="M876 71q0 21 -4.5 40.5t-9.5 36t-17.5 34.5t-21 30.5t-26.5 29.5t-27.5 25.5t-32 25.5t-31 23t-33 23t-30.5 21q-17 2 -50 2q-54 0 -106 -7t-108 -25t-98 -46t-69 -75t-27 -107q0 -68 35.5 -121.5t93 -84t120.5 -45.5t127 -15q59 0 112.5 12.5t100.5 39t74.5 73.5 t27.5 110zM756 933q0 60 -16.5 127.5t-47 130.5t-84 104t-119.5 41q-93 0 -144 -69t-51 -165q0 -47 11.5 -99t35.5 -104t56 -93.5t78 -67.5t97 -26q97 0 140.5 60.5t43.5 160.5zM625 1408h437l-135 -79h-135q71 -45 110 -126t39 -169q0 -74 -23 -131.5t-56 -92.5t-66 -64.5 t-56 -61t-23 -67.5q0 -26 16.5 -51t43 -48t58.5 -48t64 -55.5t58.5 -66t43 -85t16.5 -106.5q0 -160 -140 -282q-152 -131 -420 -131q-59 0 -119.5 10t-122 33.5t-108.5 58t-77 89t-30 121.5q0 61 37 135q32 64 96 110.5t145 71t155 36t150 13.5q-64 83 -64 149q0 12 2 23.5 t5 19.5t8 21.5t7 21.5q-40 -5 -70 -5q-149 0 -255.5 98t-106.5 246q0 140 95 250.5t234 141.5q94 20 187 20zM1664 1152v-128h-256v-256h-128v256h-256v128h256v256h128v-256h256z" />
+<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0d9;" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0da;" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0db;" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0dc;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0dd;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0de;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0e0;" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
+<glyph unicode="&#xf0e1;" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" />
+<glyph unicode="&#xf0e2;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
+<glyph unicode="&#xf0e3;" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
+<glyph unicode="&#xf0e4;" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf0e5;" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf0e6;" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
+<glyph unicode="&#xf0e7;" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
+<glyph unicode="&#xf0e8;" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
+<glyph unicode="&#xf0e9;" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0ea;" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0eb;" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
+<glyph unicode="&#xf0ec;" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf0ed;" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0ee;" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0f0;" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f1;" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf0f2;" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
+<glyph unicode="&#xf0f3;" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1664 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5 q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
+<glyph unicode="&#xf0f4;" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f5;" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f6;" horiz-adv-x="1280" d="M1024 352v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM1024 608v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280z M768 896h376q-10 29 -22 41l-313 313q-12 12 -41 22v-376zM1280 864v-896q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h640q40 0 88 -20t76 -48l312 -312q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0f7;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f8;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f9;" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0fa;" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf0fb;" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" />
+<glyph unicode="&#xf0fc;" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
+<glyph unicode="&#xf0fd;" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0fe;" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf100;" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
+<glyph unicode="&#xf101;" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf102;" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf103;" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf104;" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf105;" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf106;" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf107;" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf108;" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf109;" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
+<glyph unicode="&#xf10a;" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf10b;" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf10c;" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf10d;" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf10e;" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf110;" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" />
+<glyph unicode="&#xf111;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf112;" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
+<glyph unicode="&#xf113;" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
+<glyph unicode="&#xf114;" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf115;" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
+<glyph unicode="&#xf116;" horiz-adv-x="1792" />
+<glyph unicode="&#xf117;" horiz-adv-x="1792" />
+<glyph unicode="&#xf118;" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf119;" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf11a;" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf11b;" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" />
+<glyph unicode="&#xf11c;" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf11d;" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="&#xf11e;" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="&#xf120;" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" />
+<glyph unicode="&#xf121;" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" />
+<glyph unicode="&#xf122;" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" />
+<glyph unicode="&#xf123;" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" />
+<glyph unicode="&#xf124;" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" />
+<glyph unicode="&#xf125;" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf126;" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf127;" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" />
+<glyph unicode="&#xf128;" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" />
+<glyph unicode="&#xf129;" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf12a;" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" />
+<glyph unicode="&#xf12b;" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" />
+<glyph unicode="&#xf12c;" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" />
+<glyph unicode="&#xf12d;" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" />
+<glyph unicode="&#xf12e;" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" />
+<glyph unicode="&#xf130;" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" />
+<glyph unicode="&#xf131;" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
+<glyph unicode="&#xf132;" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf133;" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf134;" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" />
+<glyph unicode="&#xf135;" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" />
+<glyph unicode="&#xf136;" horiz-adv-x="1792" d="M1745 763l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5z" />
+<glyph unicode="&#xf137;" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf138;" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf139;" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf13a;" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf13b;" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" />
+<glyph unicode="&#xf13c;" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" />
+<glyph unicode="&#xf13d;" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf13e;" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" />
+<glyph unicode="&#xf140;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf141;" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf142;" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf143;" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf144;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" />
+<glyph unicode="&#xf145;" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" />
+<glyph unicode="&#xf146;" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
+<glyph unicode="&#xf147;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf148;" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" />
+<glyph unicode="&#xf149;" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" />
+<glyph unicode="&#xf14a;" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14b;" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14c;" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14d;" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14e;" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf150;" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf151;" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf152;" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf153;" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" />
+<glyph unicode="&#xf154;" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" />
+<glyph unicode="&#xf155;" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" />
+<glyph unicode="&#xf156;" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf157;" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" />
+<glyph unicode="&#xf158;" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" />
+<glyph unicode="&#xf159;" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf15a;" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" />
+<glyph unicode="&#xf15b;" horiz-adv-x="1280" d="M1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
+<glyph unicode="&#xf15c;" horiz-adv-x="1280" d="M1024 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1024 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28 t-28 68v1344q0 40 28 68t68 28h544v-544q0 -40 28 -68t68 -28h544zM1277 896h-509v509q82 -15 132 -65l312 -312q50 -50 65 -132z" />
+<glyph unicode="&#xf15d;" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" />
+<glyph unicode="&#xf15e;" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" />
+<glyph unicode="&#xf160;" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf161;" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf162;" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" />
+<glyph unicode="&#xf163;" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" />
+<glyph unicode="&#xf164;" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" />
+<glyph unicode="&#xf165;" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" />
+<glyph unicode="&#xf166;" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf167;" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" />
+<glyph unicode="&#xf168;" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" />
+<glyph unicode="&#xf169;" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf16a;" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
+<glyph unicode="&#xf16b;" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
+<glyph unicode="&#xf16c;" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
+<glyph unicode="&#xf16d;" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
+<glyph unicode="&#xf16e;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
+<glyph unicode="&#xf170;" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf171;" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" />
+<glyph unicode="&#xf172;" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf173;" horiz-adv-x="1024" d="M390 1408h219v-388h364v-241h-364v-394q0 -136 14 -172q13 -37 52 -60q50 -31 117 -31q117 0 232 76v-242q-102 -48 -178 -65q-77 -19 -173 -19q-105 0 -186 27q-78 25 -138 75q-58 51 -79 105q-22 54 -22 161v539h-170v217q91 30 155 84q64 55 103 132q39 78 54 196z " />
+<glyph unicode="&#xf174;" d="M1123 127v181q-88 -56 -174 -56q-51 0 -88 23q-29 17 -39 45q-11 30 -11 129v295h274v181h-274v291h-164q-11 -90 -40 -147t-78 -99q-48 -40 -116 -63v-163h127v-404q0 -78 17 -121q17 -42 59 -78q43 -37 104 -57q62 -20 140 -20q67 0 129 14q57 13 134 49zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf175;" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" />
+<glyph unicode="&#xf176;" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" />
+<glyph unicode="&#xf177;" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf178;" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" />
+<glyph unicode="&#xf179;" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" />
+<glyph unicode="&#xf17a;" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" />
+<glyph unicode="&#xf17b;" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" />
+<glyph unicode="&#xf17c;" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" />
+<glyph unicode="&#xf17d;" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf17e;" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" />
+<glyph unicode="&#xf180;" horiz-adv-x="1664" d="M1483 512l-587 -587q-52 -53 -127.5 -53t-128.5 53l-587 587q-53 53 -53 128t53 128l587 587q53 53 128 53t128 -53l265 -265l-398 -399l-188 188q-42 42 -99 42q-59 0 -100 -41l-120 -121q-42 -40 -42 -99q0 -58 42 -100l406 -408q30 -28 67 -37l6 -4h28q60 0 99 41 l619 619l2 -3q53 -53 53 -128t-53 -128zM1406 1138l120 -120q14 -15 14 -36t-14 -36l-730 -730q-17 -15 -37 -15v0q-4 0 -6 1q-18 2 -30 14l-407 408q-14 15 -14 36t14 35l121 120q13 15 35 15t36 -15l252 -252l574 575q15 15 36 15t36 -15z" />
+<glyph unicode="&#xf181;" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf182;" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf183;" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf184;" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf185;" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" />
+<glyph unicode="&#xf186;" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" />
+<glyph unicode="&#xf187;" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf188;" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" />
+<glyph unicode="&#xf189;" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" />
+<glyph unicode="&#xf18a;" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" />
+<glyph unicode="&#xf18b;" d="M1133 -34q-171 -94 -368 -94q-196 0 -367 94q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211zM638 1394v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495q0 187 83.5 349.5t229.5 269.5t325 137zM1536 638q0 -280 -181 -495 q-204 99 -330.5 306.5t-126.5 459.5v485q179 -30 325 -137t229.5 -269.5t83.5 -349.5z" />
+<glyph unicode="&#xf18c;" horiz-adv-x="1408" d="M1402 433q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32t13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5 t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56 t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5 t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5z" />
+<glyph unicode="&#xf18d;" horiz-adv-x="1280" d="M1259 283v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5v66h1238zM1259 609v-255h-1238v255h1238zM1259 937v-255h-1238v255h1238zM1259 1077v-67h-1238v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5z " />
+<glyph unicode="&#xf18e;" d="M1152 640q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf190;" d="M1152 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23t9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf191;" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf192;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf193;" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" />
+<glyph unicode="&#xf194;" d="M1254 899q16 85 -21 132q-52 65 -187 45q-17 -3 -41 -12.5t-57.5 -30.5t-64.5 -48.5t-59.5 -70t-44.5 -91.5q80 7 113.5 -16t26.5 -99q-5 -52 -52 -143q-43 -78 -71 -99q-44 -32 -87 14q-23 24 -37.5 64.5t-19 73t-10 84t-8.5 71.5q-23 129 -34 164q-12 37 -35.5 69 t-50.5 40q-57 16 -127 -25q-54 -32 -136.5 -106t-122.5 -102v-7q16 -8 25.5 -26t21.5 -20q21 -3 54.5 8.5t58 10.5t41.5 -30q11 -18 18.5 -38.5t15 -48t12.5 -40.5q17 -46 53 -187q36 -146 57 -197q42 -99 103 -125q43 -12 85 -1.5t76 31.5q131 77 250 237 q104 139 172.5 292.5t82.5 226.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf195;" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf196;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf197;" horiz-adv-x="1792" />
+<glyph unicode="&#xf198;" horiz-adv-x="1792" />
+<glyph unicode="&#xf199;" horiz-adv-x="1792" />
+<glyph unicode="&#xf19a;" horiz-adv-x="1792" />
+<glyph unicode="&#xf19b;" horiz-adv-x="1792" />
+<glyph unicode="&#xf19c;" horiz-adv-x="1792" />
+<glyph unicode="&#xf19d;" horiz-adv-x="1792" />
+<glyph unicode="&#xf19e;" horiz-adv-x="1792" />
+<glyph unicode="&#xf500;" horiz-adv-x="1792" />
+</font>
+</defs></svg> 
\ No newline at end of file
diff --git a/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf
new file mode 100644 (file)
index 0000000..e89738d
Binary files /dev/null and b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf differ
diff --git a/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff
new file mode 100644 (file)
index 0000000..8c1748a
Binary files /dev/null and b/doc/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff differ
diff --git a/doc/_themes/sphinx_rtd_theme/static/js/theme.js b/doc/_themes/sphinx_rtd_theme/static/js/theme.js
new file mode 100644 (file)
index 0000000..60520cc
--- /dev/null
@@ -0,0 +1,47 @@
+$( document ).ready(function() {
+    // Shift nav in mobile when clicking the menu.
+    $(document).on('click', "[data-toggle='wy-nav-top']", function() {
+      $("[data-toggle='wy-nav-shift']").toggleClass("shift");
+      $("[data-toggle='rst-versions']").toggleClass("shift");
+    });
+    // Close menu when you click a link.
+    $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
+      $("[data-toggle='wy-nav-shift']").removeClass("shift");
+      $("[data-toggle='rst-versions']").toggleClass("shift");
+    });
+    $(document).on('click', "[data-toggle='rst-current-version']", function() {
+      $("[data-toggle='rst-versions']").toggleClass("shift-up");
+    });  
+    // Make tables responsive
+    $("table.docutils:not(.field-list)").wrap("<div class='wy-table-responsive'></div>");
+});
+
+window.SphinxRtdTheme = (function (jquery) {
+    var stickyNav = (function () {
+        var navBar,
+            win,
+            stickyNavCssClass = 'stickynav',
+            applyStickNav = function () {
+                if (navBar.height() <= win.height()) {
+                    navBar.addClass(stickyNavCssClass);
+                } else {
+                    navBar.removeClass(stickyNavCssClass);
+                }
+            },
+            enable = function () {
+                applyStickNav();
+                win.on('resize', applyStickNav);
+            },
+            init = function () {
+                navBar = jquery('nav.wy-nav-side:first');
+                win    = jquery(window);
+            };
+        jquery(init);
+        return {
+            enable : enable
+        };
+    }());
+    return {
+        StickyNav : stickyNav
+    };
+}($));
diff --git a/doc/_themes/sphinx_rtd_theme/theme.conf b/doc/_themes/sphinx_rtd_theme/theme.conf
new file mode 100644 (file)
index 0000000..dcfbf8c
--- /dev/null
@@ -0,0 +1,8 @@
+[theme]
+inherit = basic
+stylesheet = css/theme.css
+
+[options]
+typekit_id = hiw1hhg
+analytics_id = 
+sticky_navigation = False
diff --git a/doc/_themes/sphinx_rtd_theme/versions.html b/doc/_themes/sphinx_rtd_theme/versions.html
new file mode 100644 (file)
index 0000000..8b3eb79
--- /dev/null
@@ -0,0 +1,37 @@
+{% if READTHEDOCS %}
+{# Add rst-badge after rst-versions for small badge style. #}
+  <div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
+    <span class="rst-current-version" data-toggle="rst-current-version">
+      <span class="fa fa-book"> Read the Docs</span>
+      v: {{ current_version }}
+      <span class="fa fa-caret-down"></span>
+    </span>
+    <div class="rst-other-versions">
+      <dl>
+        <dt>Versions</dt>
+        {% for slug, url in versions %}
+          <dd><a href="{{ url }}">{{ slug }}</a></dd>
+        {% endfor %}
+      </dl>
+      <dl>
+        <dt>Downloads</dt>
+        {% for type, url in downloads %}
+          <dd><a href="{{ url }}">{{ type }}</a></dd>
+        {% endfor %}
+      </dl>
+      <dl>
+        <dt>On Read the Docs</dt>
+          <dd>
+            <a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">Project Home</a>
+          </dd>
+          <dd>
+            <a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">Builds</a>
+          </dd>
+      </dl>
+      <hr/>
+      Free document hosting provided by <a href="http://www.readthedocs.org">Read the Docs</a>.
+
+    </div>
+  </div>
+{% endif %}
+
diff --git a/doc/apiref-header.rst b/doc/apiref-header.rst
new file mode 100644 (file)
index 0000000..05116ca
--- /dev/null
@@ -0,0 +1,33 @@
+API Reference
+=============
+
+Includes
+--------
+
+To use the public APIs, include ``nghttp2/nghttp2.h``::
+
+    #include <nghttp2/nghttp2.h>
+
+The header files are also available online: :doc:`nghttp2.h` and
+:doc:`nghttp2ver.h`.
+
+Remarks
+-------
+
+Do not call `nghttp2_session_send()`, `nghttp2_session_mem_send()`,
+`nghttp2_session_recv()` or `nghttp2_session_mem_recv()` from the
+nghttp2 callback functions directly or indirectly. It will lead to the
+crash.  You can submit requests or frames in the callbacks then call
+these functions outside the callbacks.
+
+Currently, `nghttp2_session_send()` and `nghttp2_session_mem_send()`
+do not send client connection preface
+(:macro:`NGHTTP2_CLIENT_CONNECTION_PREFACE`).  The applications are
+responsible to send it before sending any HTTP/2 frames using these
+functions if :type:`nghttp2_session` is configured as client.
+Similarly, `nghttp2_session_recv()` and `nghttp2_session_mem_recv()`
+do not consume client connection preface unless
+`nghttp2_option_set_recv_client_preface()` is used with nonzero option
+value.  The applications are responsible to receive it before calling
+these functions if :type:`nghttp2_session` is configured as server and
+`nghttp2_option_set_recv_client_preface()` is not used.
diff --git a/doc/asio_http2.h.rst.in b/doc/asio_http2.h.rst.in
new file mode 100644 (file)
index 0000000..645ed48
--- /dev/null
@@ -0,0 +1,5 @@
+asio_http2.h
+============
+
+.. literalinclude:: @top_srcdir@/src/includes/nghttp2/asio_http2.h
+   :language: cpp
diff --git a/doc/building-android-binary.rst.in b/doc/building-android-binary.rst.in
new file mode 100644 (file)
index 0000000..2c77c98
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/building-android-binary.rst
diff --git a/doc/conf.py.in b/doc/conf.py.in
new file mode 100644 (file)
index 0000000..0e572cf
--- /dev/null
@@ -0,0 +1,253 @@
+# -*- coding: utf-8 -*-
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#
+# nghttp2 documentation build configuration file, created by
+# sphinx-quickstart on Sun Mar 11 22:57:49 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['@top_srcdir@/_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'nghttp2'
+copyright = u'2012, 2015, Tatsuhiro Tsujikawa'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '@PACKAGE_VERSION@'
+# The full version, including alpha/beta/rc tags.
+release = '@PACKAGE_VERSION@'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['manual', 'README.rst', '*-header.rst', 'sources']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = 'c:func'
+primary_domain = 'c'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The default language to highlight source code in. The default is 'python'.
+highlight_language = 'c'
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'sphinx_rtd_theme'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['@top_srcdir@/doc/_themes']
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+#html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+html_use_smartypants = False
+
+# Custom sidebar templates, maps document names to template names.
+html_sidebars = {
+    '**': ['menu.html', 'localtoc.html', 'relations.html', 'sourcelink.html',
+           'searchbox.html']
+    }
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+html_copy_source = False
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'nghttp2doc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'nghttp2.tex', u'nghttp2 Documentation',
+   u'Tatsuhiro Tsujikawa', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('nghttp.1', 'nghttp', u'HTTP/2 experimental client',
+     [u'Tatsuhiro Tsujikawa'], 1),
+    ('nghttpd.1', 'nghttpd', u'HTTP/2 experimental server',
+     [u'Tatsuhiro Tsujikawa'], 1),
+    ('nghttpx.1', 'nghttpx', u'HTTP/2 experimental proxy',
+     [u'Tatsuhiro Tsujikawa'], 1),
+    ('h2load.1', 'h2load', u'HTTP/2 benchmarking tool',
+     [u'Tatsuhiro Tsujikawa'], 1)
+]
diff --git a/doc/contribute.rst.in b/doc/contribute.rst.in
new file mode 100644 (file)
index 0000000..6a229d4
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/contribute.rst
diff --git a/doc/h2load-howto.rst.in b/doc/h2load-howto.rst.in
new file mode 100644 (file)
index 0000000..252069d
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/h2load-howto.rst
diff --git a/doc/h2load.1 b/doc/h2load.1
new file mode 100644 (file)
index 0000000..8ba0783
--- /dev/null
@@ -0,0 +1,213 @@
+.\" Man page generated from reStructuredText.
+.
+.TH "H2LOAD" "1" "February 08, 2015" "0.7.4-DEV" "nghttp2"
+.SH NAME
+h2load \- HTTP/2 benchmarking tool
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.SH SYNOPSIS
+.sp
+\fBh2load\fP [OPTIONS]... [URI]...
+.SH DESCRIPTION
+.sp
+benchmarking tool for HTTP/2 and SPDY server
+.INDENT 0.0
+.TP
+.B <URI>
+Specify URI to access.   Multiple URIs can be specified.
+URIs are used  in this order for each  client.  All URIs
+are used, then  first URI is used and then  2nd URI, and
+so  on.  The  scheme, host  and port  in the  subsequent
+URIs, if present,  are ignored.  Those in  the first URI
+are used solely.
+.UNINDENT
+.SH OPTIONS
+.INDENT 0.0
+.TP
+.B \-n, \-\-requests=<N>
+Number of requests.
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-c, \-\-clients=<N>
+Number of concurrent clients.
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-t, \-\-threads=<N>
+Number of native threads.
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-i, \-\-input\-file=<FILE>
+Path of a file with multiple URIs are seperated by EOLs.
+This option will disable URIs getting from command\-line.
+If \(aq\-\(aq is given as <FILE>, URIs will be read from stdin.
+URIs are used  in this order for each  client.  All URIs
+are used, then  first URI is used and then  2nd URI, and
+so  on.  The  scheme, host  and port  in the  subsequent
+URIs, if present,  are ignored.  Those in  the first URI
+are used solely.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-m, \-\-max\-concurrent\-streams=(auto|<N>)
+Max concurrent streams to  issue per session.  If "auto"
+is given, the number of given URIs is used.
+.sp
+Default: \fBauto\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-w, \-\-window\-bits=<N>
+Sets the stream level initial window size to (2**<N>)\-1.
+For SPDY, 2**<N> is used instead.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-W, \-\-connection\-window\-bits=<N>
+Sets  the  connection  level   initial  window  size  to
+(2**<N>)\-1.  For SPDY, if <N>  is strictly less than 16,
+this option  is ignored.   Otherwise 2**<N> is  used for
+SPDY.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-H, \-\-header=<HEADER>
+Add/Override a header to the requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-p, \-\-no\-tls\-proto=<PROTOID>
+Specify ALPN identifier of the  protocol to be used when
+accessing http URI without SSL/TLS.
+Available protocols: spdy/2, spdy/3, spdy/3.1 and h2c\-14
+.sp
+Default: \fBh2c\-14\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-v, \-\-verbose
+Output debug information.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-version
+Display version information and exit.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-h, \-\-help
+Display this help and exit.
+.UNINDENT
+.SH OUTPUT
+.INDENT 0.0
+.TP
+.B requests
+.INDENT 7.0
+.TP
+.B total
+The number of requests h2load was instructed to make.
+.TP
+.B started
+The number of requests h2load has started.
+.TP
+.B done
+The number of requests completed.
+.TP
+.B succeeded
+The number of requests completed successfully.  Only HTTP status
+code 2xx or3xx are considered as success.
+.TP
+.B failed
+The number of requests failed, including HTTP level failures
+(non\-successful HTTP status code).
+.TP
+.B errored
+The number of requests failed, except for HTTP level failures.
+status code.  This is the subset of the number reported in
+\fBfailed\fP and most likely the network level failures or stream
+was reset by RST_STREAM.
+.UNINDENT
+.TP
+.B status codes
+The number of status code h2load received.
+.TP
+.B traffic
+.INDENT 7.0
+.TP
+.B total
+The number of bytes received from the server "on the wire".  If
+requests were made via TLS, this value is the number of decrpyted
+bytes.
+.TP
+.B headers
+The number of response header bytes from the server without
+decompression.  For HTTP/2, this is the sum of the payload of
+HEADERS frame.  For SPDY, this is the sum of the payload of
+SYN_REPLY frame.
+.TP
+.B data
+The number of response body bytes received from the server.
+.UNINDENT
+.TP
+.B time for request
+.INDENT 7.0
+.TP
+.B min
+The minimum time taken for request and response.
+.TP
+.B max
+The maximum time taken for request and response.
+.TP
+.B mean
+The mean time taken for request and response.
+.TP
+.B sd
+The standard deviation of the time for request and response.
+.TP
+.B +/\- sd
+The fraction of the number of requests within standard deviation
+range (mean +/\- sd) against total number of successful requests.
+.UNINDENT
+.UNINDENT
+.SH SEE ALSO
+.sp
+\fInghttp(1)\fP, \fInghttpd(1)\fP, \fInghttpx(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/h2load.1.rst b/doc/h2load.1.rst
new file mode 100644 (file)
index 0000000..9d66248
--- /dev/null
@@ -0,0 +1,153 @@
+
+h2load(1)
+=========
+
+SYNOPSIS
+--------
+
+**h2load** [OPTIONS]... [URI]...
+
+DESCRIPTION
+-----------
+
+benchmarking tool for HTTP/2 and SPDY server
+
+.. describe:: <URI>
+
+    Specify URI to access.   Multiple URIs can be specified.
+    URIs are used  in this order for each  client.  All URIs
+    are used, then  first URI is used and then  2nd URI, and
+    so  on.  The  scheme, host  and port  in the  subsequent
+    URIs, if present,  are ignored.  Those in  the first URI
+    are used solely.
+
+OPTIONS
+-------
+
+.. option:: -n, --requests=<N>
+
+    Number of requests.
+
+    Default: ``1``
+
+.. option:: -c, --clients=<N>
+
+    Number of concurrent clients.
+
+    Default: ``1``
+
+.. option:: -t, --threads=<N>
+
+    Number of native threads.
+
+    Default: ``1``
+
+.. option:: -i, --input-file=<FILE>
+
+    Path of a file with multiple URIs are seperated by EOLs.
+    This option will disable URIs getting from command-line.
+    If '-' is given as <FILE>, URIs will be read from stdin.
+    URIs are used  in this order for each  client.  All URIs
+    are used, then  first URI is used and then  2nd URI, and
+    so  on.  The  scheme, host  and port  in the  subsequent
+    URIs, if present,  are ignored.  Those in  the first URI
+    are used solely.
+
+.. option:: -m, --max-concurrent-streams=(auto|<N>)
+
+    Max concurrent streams to  issue per session.  If "auto"
+    is given, the number of given URIs is used.
+
+    Default: ``auto``
+
+.. option:: -w, --window-bits=<N>
+
+    Sets the stream level initial window size to (2\*\*<N>)-1.
+    For SPDY, 2**<N> is used instead.
+
+.. option:: -W, --connection-window-bits=<N>
+
+    Sets  the  connection  level   initial  window  size  to
+    (2**<N>)-1.  For SPDY, if <N>  is strictly less than 16,
+    this option  is ignored.   Otherwise 2\*\*<N> is  used for
+    SPDY.
+
+.. option:: -H, --header=<HEADER>
+
+    Add/Override a header to the requests.
+
+.. option:: -p, --no-tls-proto=<PROTOID>
+
+    Specify ALPN identifier of the  protocol to be used when
+    accessing http URI without SSL/TLS.
+    Available protocols: spdy/2, spdy/3, spdy/3.1 and h2c-14
+
+    Default: ``h2c-14``
+
+.. option:: -v, --verbose
+
+    Output debug information.
+
+.. option:: --version
+
+    Display version information and exit.
+
+.. option:: -h, --help
+
+    Display this help and exit.
+
+OUTPUT
+------
+
+requests
+  total
+    The number of requests h2load was instructed to make.
+  started
+    The number of requests h2load has started.
+  done
+    The number of requests completed.
+  succeeded
+    The number of requests completed successfully.  Only HTTP status
+    code 2xx or3xx are considered as success.
+  failed
+    The number of requests failed, including HTTP level failures
+    (non-successful HTTP status code).
+  errored
+    The number of requests failed, except for HTTP level failures.
+    status code.  This is the subset of the number reported in
+    ``failed`` and most likely the network level failures or stream
+    was reset by RST_STREAM.
+
+status codes
+  The number of status code h2load received.
+
+traffic
+  total
+    The number of bytes received from the server "on the wire".  If
+    requests were made via TLS, this value is the number of decrpyted
+    bytes.
+  headers
+    The number of response header bytes from the server without
+    decompression.  For HTTP/2, this is the sum of the payload of
+    HEADERS frame.  For SPDY, this is the sum of the payload of
+    SYN_REPLY frame.
+  data
+    The number of response body bytes received from the server.
+
+time for request
+  min
+    The minimum time taken for request and response.
+  max
+    The maximum time taken for request and response.
+  mean
+    The mean time taken for request and response.
+  sd
+    The standard deviation of the time for request and response.
+  +/- sd
+    The fraction of the number of requests within standard deviation
+    range (mean +/- sd) against total number of successful requests.
+
+SEE ALSO
+--------
+
+:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`
diff --git a/doc/h2load.h2r b/doc/h2load.h2r
new file mode 100644 (file)
index 0000000..ccefdd1
--- /dev/null
@@ -0,0 +1,55 @@
+OUTPUT
+------
+
+requests
+  total
+    The number of requests h2load was instructed to make.
+  started
+    The number of requests h2load has started.
+  done
+    The number of requests completed.
+  succeeded
+    The number of requests completed successfully.  Only HTTP status
+    code 2xx or3xx are considered as success.
+  failed
+    The number of requests failed, including HTTP level failures
+    (non-successful HTTP status code).
+  errored
+    The number of requests failed, except for HTTP level failures.
+    status code.  This is the subset of the number reported in
+    ``failed`` and most likely the network level failures or stream
+    was reset by RST_STREAM.
+
+status codes
+  The number of status code h2load received.
+
+traffic
+  total
+    The number of bytes received from the server "on the wire".  If
+    requests were made via TLS, this value is the number of decrpyted
+    bytes.
+  headers
+    The number of response header bytes from the server without
+    decompression.  For HTTP/2, this is the sum of the payload of
+    HEADERS frame.  For SPDY, this is the sum of the payload of
+    SYN_REPLY frame.
+  data
+    The number of response body bytes received from the server.
+
+time for request
+  min
+    The minimum time taken for request and response.
+  max
+    The maximum time taken for request and response.
+  mean
+    The mean time taken for request and response.
+  sd
+    The standard deviation of the time for request and response.
+  +/- sd
+    The fraction of the number of requests within standard deviation
+    range (mean +/- sd) against total number of successful requests.
+
+SEE ALSO
+--------
+
+:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`
diff --git a/doc/index.rst.in b/doc/index.rst.in
new file mode 100644 (file)
index 0000000..2a49369
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/index.rst
diff --git a/doc/libnghttp2_asio.rst.in b/doc/libnghttp2_asio.rst.in
new file mode 100644 (file)
index 0000000..38254e1
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/libnghttp2_asio.rst
diff --git a/doc/make.bat b/doc/make.bat
new file mode 100644 (file)
index 0000000..2e0500d
--- /dev/null
@@ -0,0 +1,170 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+       set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+       set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+       :help
+       echo.Please use `make ^<target^>` where ^<target^> is one of
+       echo.  html       to make standalone HTML files
+       echo.  dirhtml    to make HTML files named index.html in directories
+       echo.  singlehtml to make a single large HTML file
+       echo.  pickle     to make pickle files
+       echo.  json       to make JSON files
+       echo.  htmlhelp   to make HTML files and a HTML help project
+       echo.  qthelp     to make HTML files and a qthelp project
+       echo.  devhelp    to make HTML files and a Devhelp project
+       echo.  epub       to make an epub
+       echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+       echo.  text       to make text files
+       echo.  man        to make manual pages
+       echo.  changes    to make an overview over all changed/added/deprecated items
+       echo.  linkcheck  to check all external links for integrity
+       echo.  doctest    to run all doctests embedded in the documentation if enabled
+       goto end
+)
+
+if "%1" == "clean" (
+       for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+       del /q /s %BUILDDIR%\*
+       goto end
+)
+
+if "%1" == "html" (
+       %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+       goto end
+)
+
+if "%1" == "dirhtml" (
+       %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+       goto end
+)
+
+if "%1" == "singlehtml" (
+       %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+       goto end
+)
+
+if "%1" == "pickle" (
+       %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can process the pickle files.
+       goto end
+)
+
+if "%1" == "json" (
+       %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can process the JSON files.
+       goto end
+)
+
+if "%1" == "htmlhelp" (
+       %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+       goto end
+)
+
+if "%1" == "qthelp" (
+       %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+       echo.^> qcollectiongenerator %BUILDDIR%\qthelp\nghttp2.qhcp
+       echo.To view the help file:
+       echo.^> assistant -collectionFile %BUILDDIR%\qthelp\nghttp2.ghc
+       goto end
+)
+
+if "%1" == "devhelp" (
+       %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished.
+       goto end
+)
+
+if "%1" == "epub" (
+       %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The epub file is in %BUILDDIR%/epub.
+       goto end
+)
+
+if "%1" == "latex" (
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+       goto end
+)
+
+if "%1" == "text" (
+       %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The text files are in %BUILDDIR%/text.
+       goto end
+)
+
+if "%1" == "man" (
+       %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The manual pages are in %BUILDDIR%/man.
+       goto end
+)
+
+if "%1" == "changes" (
+       %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.The overview file is in %BUILDDIR%/changes.
+       goto end
+)
+
+if "%1" == "linkcheck" (
+       %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+       goto end
+)
+
+if "%1" == "doctest" (
+       %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+       goto end
+)
+
+:end
diff --git a/doc/mkapiref.py b/doc/mkapiref.py
new file mode 100755 (executable)
index 0000000..1d55242
--- /dev/null
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# Generates API reference from C source code.
+from __future__ import print_function # At least python 2.6 is required
+import re, sys, argparse
+
+class FunctionDoc:
+    def __init__(self, name, content, domain):
+        self.name = name
+        self.content = content
+        self.domain = domain
+
+    def write(self, out):
+        print('''.. {}:: {}'''.format(self.domain, self.name))
+        print('')
+        for line in self.content:
+            print('    {}'.format(line))
+
+class StructDoc:
+    def __init__(self, name, content, members, member_domain):
+        self.name = name
+        self.content = content
+        self.members = members
+        self.member_domain = member_domain
+
+    def write(self, out):
+        if self.name:
+            print('''.. type:: {}'''.format(self.name))
+            print('')
+            for line in self.content:
+                print('    {}'.format(line))
+            print('')
+            for name, content in self.members:
+                print('''    .. {}:: {}'''.format(self.member_domain, name))
+                print('')
+                for line in content:
+                    print('''        {}'''.format(line))
+            print('')
+
+class MacroDoc:
+    def __init__(self, name, content):
+        self.name = name
+        self.content = content
+
+    def write(self, out):
+        print('''.. macro:: {}'''.format(self.name))
+        print('')
+        for line in self.content:
+            print('    {}'.format(line))
+
+def make_api_ref(infiles):
+    macros = []
+    enums = []
+    types = []
+    functions = []
+    for infile in infiles:
+        while True:
+            line = infile.readline()
+            if not line:
+                break
+            elif line == '/**\n':
+                line = infile.readline()
+                doctype = line.split()[1]
+                if doctype == '@function':
+                    functions.append(process_function('function', infile))
+                elif doctype == '@functypedef':
+                    types.append(process_function('type', infile))
+                elif doctype == '@struct' or doctype == '@union':
+                    types.append(process_struct(infile))
+                elif doctype == '@enum':
+                    enums.append(process_enum(infile))
+                elif doctype == '@macro':
+                    macros.append(process_macro(infile))
+    alldocs = [('Macros', macros),
+               ('Enums', enums),
+               ('Types (structs, unions and typedefs)', types),
+               ('Functions', functions)]
+    for title, docs in alldocs:
+        if not docs:
+            continue
+        print(title)
+        print('-'*len(title))
+        for doc in docs:
+            doc.write(sys.stdout)
+            print('')
+        print('')
+
+def process_macro(infile):
+    content = read_content(infile)
+    line = infile.readline()
+    macro_name = line.split()[1]
+    return MacroDoc(macro_name, content)
+
+def process_enum(infile):
+    members = []
+    enum_name = None
+    content = read_content(infile)
+    while True:
+        line = infile.readline()
+        if not line:
+            break
+        elif re.match(r'\s*/\*\*\n', line):
+            member_content = read_content(infile)
+            line = infile.readline()
+            items = line.split()
+            member_name = items[0]
+            if len(items) >= 3:
+                member_content.insert(0, '(``{}``) '\
+                                      .format(' '.join(items[2:]).rstrip(',')))
+            members.append((member_name, member_content))
+        elif line.startswith('}'):
+            enum_name = line.rstrip().split()[1]
+            enum_name = re.sub(r';$', '', enum_name)
+            break
+    return StructDoc(enum_name, content, members, 'macro')
+
+def process_struct(infile):
+    members = []
+    struct_name = None
+    content = read_content(infile)
+    while True:
+        line = infile.readline()
+        if not line:
+            break
+        elif re.match(r'\s*/\*\*\n', line):
+            member_content = read_content(infile)
+            line = infile.readline()
+            member_name = line.rstrip().rstrip(';')
+            members.append((member_name, member_content))
+        elif line.startswith('}') or\
+                (line.startswith('typedef ') and line.endswith(';\n')):
+            if line.startswith('}'):
+                index = 1
+            else:
+                index = 3
+            struct_name = line.rstrip().split()[index]
+            struct_name = re.sub(r';$', '', struct_name)
+            break
+    return StructDoc(struct_name, content, members, 'member')
+
+def process_function(domain, infile):
+    content = read_content(infile)
+    func_proto = []
+    while True:
+        line = infile.readline()
+        if not line:
+            break
+        elif line == '\n':
+            break
+        else:
+            func_proto.append(line)
+    func_proto = ''.join(func_proto)
+    func_proto = re.sub(r';\n$', '', func_proto)
+    func_proto = re.sub(r'\s+', ' ', func_proto)
+    return FunctionDoc(func_proto, content, domain)
+
+def read_content(infile):
+    content = []
+    while True:
+        line = infile.readline()
+        if not line:
+            break
+        if re.match(r'\s*\*/\n', line):
+            break
+        else:
+            content.append(transform_content(line.rstrip()))
+    return content
+
+def arg_repl(matchobj):
+    return '*{}*'.format(matchobj.group(1).replace('*', '\\*'))
+
+def transform_content(content):
+    content = re.sub(r'^\s+\* ?', '', content)
+    content = re.sub(r'\|([^\s|]+)\|', arg_repl, content)
+    content = re.sub(r':enum:', ':macro:', content)
+    return content
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description="Generate API reference")
+    parser.add_argument('--header', type=argparse.FileType('r'),
+                        help='header inserted at the top of the page')
+    parser.add_argument('files', nargs='+', type=argparse.FileType('r'),
+                        help='source file')
+    args = parser.parse_args()
+    if args.header:
+        print(args.header.read())
+    for infile in args.files:
+        make_api_ref(args.files)
diff --git a/doc/nghttp.1 b/doc/nghttp.1
new file mode 100644 (file)
index 0000000..104f6de
--- /dev/null
@@ -0,0 +1,211 @@
+.\" Man page generated from reStructuredText.
+.
+.TH "NGHTTP" "1" "February 08, 2015" "0.7.4-DEV" "nghttp2"
+.SH NAME
+nghttp \- HTTP/2 experimental client
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.SH SYNOPSIS
+.sp
+\fBnghttp\fP [OPTIONS]... <URI>...
+.SH DESCRIPTION
+.sp
+HTTP/2 experimental client
+.INDENT 0.0
+.TP
+.B <URI>
+Specify URI to access.
+.UNINDENT
+.SH OPTIONS
+.INDENT 0.0
+.TP
+.B \-v, \-\-verbose
+Print   debug   information   such  as   reception   and
+transmission of frames and name/value pairs.  Specifying
+this option multiple times increases verbosity.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-n, \-\-null\-out
+Discard downloaded data.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-O, \-\-remote\-name
+Save  download  data  in  the  current  directory.   The
+filename is  dereived from URI.   If URI ends  with \(aq\fI/\fP\(aq,
+\(aqindex.html\(aq  is used  as a  filename.  Not  implemented
+yet.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-t, \-\-timeout=<SEC>
+Timeout each request after <SEC> seconds.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-w, \-\-window\-bits=<N>
+Sets the stream level initial window size to 2**<N>\-1.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-W, \-\-connection\-window\-bits=<N>
+Sets  the  connection  level   initial  window  size  to
+2**<N>\-1.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-a, \-\-get\-assets
+Download assets  such as stylesheets, images  and script
+files linked  from the downloaded resource.   Only links
+whose  origins are  the same  with the  linking resource
+will be downloaded.   nghttp prioritizes resources using
+HTTP/2 dependency  based priority.  The  priority order,
+from highest to lowest,  is html itself, css, javascript
+and images.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-s, \-\-stat
+Print statistics.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-H, \-\-header=<HEADER>
+Add a header to the requests.  Example: \fI\%\-H\fP\(aq:method: PUT\(aq
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-cert=<CERT>
+Use  the specified  client certificate  file.  The  file
+must be in PEM format.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-key=<KEY>
+Use the  client private key  file.  The file must  be in
+PEM format.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-d, \-\-data=<FILE>
+Post FILE to server. If \(aq\-\(aq  is given, data will be read
+from stdin.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-m, \-\-multiply=<N>
+Request each URI <N> times.  By default, same URI is not
+requested twice.  This option disables it too.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-u, \-\-upgrade
+Perform HTTP Upgrade for HTTP/2.  This option is ignored
+if the request URI has https scheme.  If \fI\-d\fP is used, the
+HTTP upgrade request is performed with OPTIONS method.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-p, \-\-weight=<WEIGHT>
+Sets priority group weight.  The valid value range is
+[1, 256], inclusive.
+.sp
+Default: \fB16\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-M, \-\-peer\-max\-concurrent\-streams=<N>
+Use  <N>  as  SETTINGS_MAX_CONCURRENT_STREAMS  value  of
+remote endpoint as if it  is received in SETTINGS frame.
+The default is large enough as it is seen as unlimited.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-c, \-\-header\-table\-size=<SIZE>
+Specify decoder header table size.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-b, \-\-padding=<N>
+Add at  most <N>  bytes to a  frame payload  as padding.
+Specify 0 to disable padding.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-r, \-\-har=<FILE>
+Output HTTP  transactions <FILE> in HAR  format.  If \(aq\-\(aq
+is given, data is written to stdout.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-color
+Force colored log output.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-continuation
+Send large header to test CONTINUATION.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-content\-length
+Don\(aqt send content\-length header field.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-dep
+Don\(aqt send dependency based priority hint to server.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-dep\-idle
+Use idle streams as anchor nodes to express priority.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-version
+Display version information and exit.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-h, \-\-help
+Display this help and exit.
+.UNINDENT
+.sp
+The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+10 * 1024).  Units are K, M and G (powers of 1024).
+.SH SEE ALSO
+.sp
+\fInghttpd(1)\fP, \fInghttpx(1)\fP, \fIh2load(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/nghttp.1.rst b/doc/nghttp.1.rst
new file mode 100644 (file)
index 0000000..77ef39b
--- /dev/null
@@ -0,0 +1,158 @@
+
+nghttp(1)
+=========
+
+SYNOPSIS
+--------
+
+**nghttp** [OPTIONS]... <URI>...
+
+DESCRIPTION
+-----------
+
+HTTP/2 experimental client
+
+.. describe:: <URI>
+
+    Specify URI to access.
+
+OPTIONS
+-------
+
+.. option:: -v, --verbose
+
+    Print   debug   information   such  as   reception   and
+    transmission of frames and name/value pairs.  Specifying
+    this option multiple times increases verbosity.
+
+.. option:: -n, --null-out
+
+    Discard downloaded data.
+
+.. option:: -O, --remote-name
+
+    Save  download  data  in  the  current  directory.   The
+    filename is  dereived from URI.   If URI ends  with '*/*',
+    'index.html'  is used  as a  filename.  Not  implemented
+    yet.
+
+.. option:: -t, --timeout=<SEC>
+
+    Timeout each request after <SEC> seconds.
+
+.. option:: -w, --window-bits=<N>
+
+    Sets the stream level initial window size to 2\*\*<N>-1.
+
+.. option:: -W, --connection-window-bits=<N>
+
+    Sets  the  connection  level   initial  window  size  to
+    2\*\*<N>-1.
+
+.. option:: -a, --get-assets
+
+    Download assets  such as stylesheets, images  and script
+    files linked  from the downloaded resource.   Only links
+    whose  origins are  the same  with the  linking resource
+    will be downloaded.   nghttp prioritizes resources using
+    HTTP/2 dependency  based priority.  The  priority order,
+    from highest to lowest,  is html itself, css, javascript
+    and images.
+
+.. option:: -s, --stat
+
+    Print statistics.
+
+.. option:: -H, --header=<HEADER>
+
+    Add a header to the requests.  Example: :option:`-H`\':method: PUT'
+
+.. option:: --cert=<CERT>
+
+    Use  the specified  client certificate  file.  The  file
+    must be in PEM format.
+
+.. option:: --key=<KEY>
+
+    Use the  client private key  file.  The file must  be in
+    PEM format.
+
+.. option:: -d, --data=<FILE>
+
+    Post FILE to server. If '-'  is given, data will be read
+    from stdin.
+
+.. option:: -m, --multiply=<N>
+
+    Request each URI <N> times.  By default, same URI is not
+    requested twice.  This option disables it too.
+
+.. option:: -u, --upgrade
+
+    Perform HTTP Upgrade for HTTP/2.  This option is ignored
+    if the request URI has https scheme.  If :option:`-d` is used, the
+    HTTP upgrade request is performed with OPTIONS method.
+
+.. option:: -p, --weight=<WEIGHT>
+
+    Sets priority group weight.  The valid value range is
+    [1, 256], inclusive.
+
+    Default: ``16``
+
+.. option:: -M, --peer-max-concurrent-streams=<N>
+
+    Use  <N>  as  SETTINGS_MAX_CONCURRENT_STREAMS  value  of
+    remote endpoint as if it  is received in SETTINGS frame.
+    The default is large enough as it is seen as unlimited.
+
+.. option:: -c, --header-table-size=<SIZE>
+
+    Specify decoder header table size.
+
+.. option:: -b, --padding=<N>
+
+    Add at  most <N>  bytes to a  frame payload  as padding.
+    Specify 0 to disable padding.
+
+.. option:: -r, --har=<FILE>
+
+    Output HTTP  transactions <FILE> in HAR  format.  If '-'
+    is given, data is written to stdout.
+
+.. option:: --color
+
+    Force colored log output.
+
+.. option:: --continuation
+
+    Send large header to test CONTINUATION.
+
+.. option:: --no-content-length
+
+    Don't send content-length header field.
+
+.. option:: --no-dep
+
+    Don't send dependency based priority hint to server.
+
+.. option:: --dep-idle
+
+    Use idle streams as anchor nodes to express priority.
+
+.. option:: --version
+
+    Display version information and exit.
+
+.. option:: -h, --help
+
+    Display this help and exit.
+
+
+The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+10 * 1024).  Units are K, M and G (powers of 1024).
+
+SEE ALSO
+--------
+
+:manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)`
diff --git a/doc/nghttp.h2r b/doc/nghttp.h2r
new file mode 100644 (file)
index 0000000..a27f416
--- /dev/null
@@ -0,0 +1,4 @@
+SEE ALSO
+--------
+
+:manpage:`nghttpd(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)`
diff --git a/doc/nghttp2.h.rst.in b/doc/nghttp2.h.rst.in
new file mode 100644 (file)
index 0000000..29e641d
--- /dev/null
@@ -0,0 +1,4 @@
+nghttp2.h
+=========
+
+.. literalinclude:: @top_srcdir@/lib/includes/nghttp2/nghttp2.h
diff --git a/doc/nghttp2ver.h.rst.in b/doc/nghttp2ver.h.rst.in
new file mode 100644 (file)
index 0000000..c6aa779
--- /dev/null
@@ -0,0 +1,4 @@
+nghttp2ver.h
+============
+
+.. literalinclude:: @top_builddir@/lib/includes/nghttp2/nghttp2ver.h
diff --git a/doc/nghttpd.1 b/doc/nghttpd.1
new file mode 100644 (file)
index 0000000..2523ca8
--- /dev/null
@@ -0,0 +1,160 @@
+.\" Man page generated from reStructuredText.
+.
+.TH "NGHTTPD" "1" "February 08, 2015" "0.7.4-DEV" "nghttp2"
+.SH NAME
+nghttpd \- HTTP/2 experimental server
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.SH SYNOPSIS
+.sp
+\fBnghttpd\fP [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]
+.SH DESCRIPTION
+.sp
+HTTP/2 experimental server
+.INDENT 0.0
+.TP
+.B <PORT>
+Specify listening port number.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B <PRIVATE_KEY>
+Set  path  to  server\(aqs private  key.   Required  unless
+\fI\%\-\-no\-tls\fP is specified.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B <CERT>
+Set  path  to  server\(aqs  certificate.   Required  unless
+\fI\%\-\-no\-tls\fP is specified.
+.UNINDENT
+.SH OPTIONS
+.INDENT 0.0
+.TP
+.B \-D, \-\-daemon
+Run in a background.  If \fI\-D\fP is used, the current working
+directory is  changed to \(aq\fI/\fP\(aq.  Therefore  if this option
+is used, \fI\%\-d\fP option must be specified.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-V, \-\-verify\-client
+The server  sends a client certificate  request.  If the
+client did  not return  a certificate, the  handshake is
+terminated.   Currently,  this  option just  requests  a
+client certificate and does not verify it.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-d, \-\-htdocs=<PATH>
+Specify document root.  If this option is not specified,
+the document root is the current working directory.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-v, \-\-verbose
+Print debug information  such as reception/ transmission
+of frames and name/value pairs.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-tls
+Disable SSL/TLS.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-c, \-\-header\-table\-size=<SIZE>
+Specify decoder header table size.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-color
+Force colored log output.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-p, \-\-push=<PATH>=<PUSH_PATH,...>
+Push  resources <PUSH_PATH>s  when <PATH>  is requested.
+This option  can be used repeatedly  to specify multiple
+push  configurations.    <PATH>  and   <PUSH_PATH>s  are
+relative  to   document  root.   See   \fI\%\-\-htdocs\fP  option.
+Example: \fI\-p\fP/=/foo.png \fI\-p\fP/doc=/bar.css
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-b, \-\-padding=<N>
+Add at  most <N>  bytes to a  frame payload  as padding.
+Specify 0 to disable padding.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-n, \-\-workers=<N>
+Set the number of worker threads.
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-e, \-\-error\-gzip
+Make error response gzipped.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-dh\-param\-file=<PATH>
+Path to file that contains  DH parameters in PEM format.
+Without  this   option,  DHE   cipher  suites   are  not
+available.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-early\-response
+Start sending response when request HEADERS is received,
+rather than complete request is received.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-version
+Display version information and exit.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-h, \-\-help
+Display this help and exit.
+.UNINDENT
+.sp
+The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+10 * 1024).  Units are K, M and G (powers of 1024).
+.SH SEE ALSO
+.sp
+\fInghttp(1)\fP, \fInghttpx(1)\fP, \fIh2load(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/nghttpd.1.rst b/doc/nghttpd.1.rst
new file mode 100644 (file)
index 0000000..522530f
--- /dev/null
@@ -0,0 +1,117 @@
+
+nghttpd(1)
+==========
+
+SYNOPSIS
+--------
+
+**nghttpd** [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]
+
+DESCRIPTION
+-----------
+
+HTTP/2 experimental server
+
+.. describe:: <PORT>
+
+    Specify listening port number.
+
+.. describe:: <PRIVATE_KEY>
+
+    
+    Set  path  to  server's private  key.   Required  unless
+    :option:`--no-tls` is specified.
+
+.. describe:: <CERT>
+
+    Set  path  to  server's  certificate.   Required  unless
+    :option:`--no-tls` is specified.
+
+OPTIONS
+-------
+
+.. option:: -D, --daemon
+
+    Run in a background.  If :option:`-D` is used, the current working
+    directory is  changed to '*/*'.  Therefore  if this option
+    is used, :option:`-d` option must be specified.
+
+.. option:: -V, --verify-client
+
+    The server  sends a client certificate  request.  If the
+    client did  not return  a certificate, the  handshake is
+    terminated.   Currently,  this  option just  requests  a
+    client certificate and does not verify it.
+
+.. option:: -d, --htdocs=<PATH>
+
+    Specify document root.  If this option is not specified,
+    the document root is the current working directory.
+
+.. option:: -v, --verbose
+
+    Print debug information  such as reception/ transmission
+    of frames and name/value pairs.
+
+.. option:: --no-tls
+
+    Disable SSL/TLS.
+
+.. option:: -c, --header-table-size=<SIZE>
+
+    Specify decoder header table size.
+
+.. option:: --color
+
+    Force colored log output.
+
+.. option:: -p, --push=<PATH>=<PUSH_PATH,...>
+
+    Push  resources <PUSH_PATH>s  when <PATH>  is requested.
+    This option  can be used repeatedly  to specify multiple
+    push  configurations.    <PATH>  and   <PUSH_PATH>s  are
+    relative  to   document  root.   See   :option:`--htdocs`  option.
+    Example: :option:`-p`\/=/foo.png :option:`-p`\/doc=/bar.css
+
+.. option:: -b, --padding=<N>
+
+    Add at  most <N>  bytes to a  frame payload  as padding.
+    Specify 0 to disable padding.
+
+.. option:: -n, --workers=<N>
+
+    Set the number of worker threads.
+
+    Default: ``1``
+
+.. option:: -e, --error-gzip
+
+    Make error response gzipped.
+
+.. option:: --dh-param-file=<PATH>
+
+    Path to file that contains  DH parameters in PEM format.
+    Without  this   option,  DHE   cipher  suites   are  not
+    available.
+
+.. option:: --early-response
+
+    Start sending response when request HEADERS is received,
+    rather than complete request is received.
+
+.. option:: --version
+
+    Display version information and exit.
+
+.. option:: -h, --help
+
+    Display this help and exit.
+
+
+The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+10 * 1024).  Units are K, M and G (powers of 1024).
+
+SEE ALSO
+--------
+
+:manpage:`nghttp(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)`
diff --git a/doc/nghttpd.h2r b/doc/nghttpd.h2r
new file mode 100644 (file)
index 0000000..e346cd1
--- /dev/null
@@ -0,0 +1,4 @@
+SEE ALSO
+--------
+
+:manpage:`nghttp(1)`, :manpage:`nghttpx(1)`, :manpage:`h2load(1)`
diff --git a/doc/nghttpx-howto.rst.in b/doc/nghttpx-howto.rst.in
new file mode 100644 (file)
index 0000000..082ce51
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/nghttpx-howto.rst
diff --git a/doc/nghttpx.1 b/doc/nghttpx.1
new file mode 100644 (file)
index 0000000..d6b2b44
--- /dev/null
@@ -0,0 +1,841 @@
+.\" Man page generated from reStructuredText.
+.
+.TH "NGHTTPX" "1" "February 08, 2015" "0.7.4-DEV" "nghttp2"
+.SH NAME
+nghttpx \- HTTP/2 experimental proxy
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.SH SYNOPSIS
+.sp
+\fBnghttpx\fP [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+.SH DESCRIPTION
+.sp
+A reverse proxy for HTTP/2, HTTP/1 and SPDY.
+.INDENT 0.0
+.TP
+.B <PRIVATE_KEY>
+Set path  to server\(aqs private key.   Required unless \fI\%\-p\fP,
+\fI\%\-\-client\fP or \fI\%\-\-frontend\-no\-tls\fP are given.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B <CERT>
+Set path  to server\(aqs certificate.  Required  unless \fI\%\-p\fP,
+\fI\%\-\-client\fP or \fI\%\-\-frontend\-no\-tls\fP are given.
+.UNINDENT
+.SH OPTIONS
+.sp
+The options are categorized into several groups.
+.SS Connections
+.INDENT 0.0
+.TP
+.B \-b, \-\-backend=<HOST,PORT>
+Set backend host and port.  For HTTP/1 backend, multiple
+backend addresses are accepted by repeating this option.
+HTTP/2  backend   does  not  support   multiple  backend
+addresses  and the  first occurrence  of this  option is
+used.
+.sp
+Default: \fB127.0.0.1,80\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-f, \-\-frontend=<HOST,PORT>
+Set  frontend  host and  port.   If  <HOST> is  \(aq*\(aq,  it
+assumes all addresses including both IPv4 and IPv6.
+.sp
+Default: \fB*,3000\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backlog=<N>
+Set listen backlog size.
+.sp
+Default: \fB512\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-ipv4
+Resolve backend hostname to IPv4 address only.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-ipv6
+Resolve backend hostname to IPv6 address only.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http\-proxy\-uri=<URI>
+Specify      proxy       URI      in       the      form
+\fI\%http:/\fP/[<USER>:<PASS>@]<PROXY>:<PORT>.    If   a   proxy
+requires  authentication,  specify  <USER>  and  <PASS>.
+Note that  they must be properly  percent\-encoded.  This
+proxy  is used  when the  backend connection  is HTTP/2.
+First,  make  a CONNECT  request  to  the proxy  and  it
+connects  to the  backend  on behalf  of nghttpx.   This
+forms  tunnel.   After  that, nghttpx  performs  SSL/TLS
+handshake with  the downstream through the  tunnel.  The
+timeouts when connecting and  making CONNECT request can
+be     specified    by     \fI\%\-\-backend\-read\-timeout\fP    and
+\fI\%\-\-backend\-write\-timeout\fP options.
+.UNINDENT
+.SS Performance
+.INDENT 0.0
+.TP
+.B \-n, \-\-workers=<N>
+Set the number of worker threads.
+.sp
+Default: \fB1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-read\-rate=<SIZE>
+Set maximum  average read  rate on  frontend connection.
+Setting 0 to this option means read rate is unlimited.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-read\-burst=<SIZE>
+Set  maximum read  burst  size  on frontend  connection.
+Setting  0  to this  option  means  read burst  size  is
+unlimited.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-write\-rate=<SIZE>
+Set maximum  average write rate on  frontend connection.
+Setting 0 to this option means write rate is unlimited.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-write\-burst=<SIZE>
+Set  maximum write  burst size  on frontend  connection.
+Setting  0 to  this  option means  write  burst size  is
+unlimited.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-worker\-read\-rate=<SIZE>
+Set maximum average read rate on frontend connection per
+worker.  Setting  0 to  this option  means read  rate is
+unlimited.  Not implemented yet.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-worker\-read\-burst=<SIZE>
+Set maximum  read burst size on  frontend connection per
+worker.  Setting 0 to this  option means read burst size
+is unlimited.  Not implemented yet.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-worker\-write\-rate=<SIZE>
+Set maximum  average write  rate on  frontend connection
+per worker.  Setting  0 to this option  means write rate
+is unlimited.  Not implemented yet.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-worker\-write\-burst=<SIZE>
+Set maximum write burst  size on frontend connection per
+worker.  Setting 0 to this option means write burst size
+is unlimited.  Not implemented yet.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-worker\-frontend\-connections=<N>
+Set maximum number  of simultaneous connections frontend
+accepts.  Setting 0 means unlimited.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http1\-connections\-per\-host=<N>
+Set   maximum  number   of  backend   concurrent  HTTP/1
+connections per host.  This option is meaningful when \fI\%\-s\fP
+option is used.  To limit  the number of connections per
+frontend        for       default        mode,       use
+\fI\%\-\-backend\-http1\-connections\-per\-frontend\fP\&.
+.sp
+Default: \fB8\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http1\-connections\-per\-frontend=<N>
+Set   maximum  number   of  backend   concurrent  HTTP/1
+connections per frontend.  This  option is only used for
+default mode.   0 means unlimited.  To  limit the number
+of connections  per host for  HTTP/2 or SPDY  proxy mode
+(\-s option), use \fI\%\-\-backend\-http1\-connections\-per\-host\fP\&.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-rlimit\-nofile=<N>
+Set maximum number of open files (RLIMIT_NOFILE) to <N>.
+If 0 is given, nghttpx does not set the limit.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-request\-buffer=<SIZE>
+Set buffer size used to store backend request.
+.sp
+Default: \fB16K\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-response\-buffer=<SIZE>
+Set buffer size used to store backend response.
+.sp
+Default: \fB16K\fP
+.UNINDENT
+.SS Timeout
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-read\-timeout=<DURATION>
+Specify  read  timeout  for  HTTP/2  and  SPDY  frontend
+connection.
+.sp
+Default: \fB180s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-read\-timeout=<DURATION>
+Specify read timeout for HTTP/1.1 frontend connection.
+.sp
+Default: \fB180s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-write\-timeout=<DURATION>
+Specify write timeout for all frontend connections.
+.sp
+Default: \fB30s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-stream\-read\-timeout=<DURATION>
+Specify  read timeout  for HTTP/2  and SPDY  streams.  0
+means no timeout.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-stream\-write\-timeout=<DURATION>
+Specify write  timeout for  HTTP/2 and SPDY  streams.  0
+means no timeout.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-read\-timeout=<DURATION>
+Specify read timeout for backend connection.
+.sp
+Default: \fB180s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-write\-timeout=<DURATION>
+Specify write timeout for backend connection.
+.sp
+Default: \fB30s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-keep\-alive\-timeout=<DURATION>
+Specify keep\-alive timeout for backend connection.
+.sp
+Default: \fB2s\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-listener\-disable\-timeout=<DURATION>
+After accepting  connection failed,  connection listener
+is disabled  for a given  amount of time.   Specifying 0
+disables this feature.
+.sp
+Default: \fB0\fP
+.UNINDENT
+.SS SSL/TLS
+.INDENT 0.0
+.TP
+.B \-\-ciphers=<SUITE>
+Set allowed  cipher list.  The  format of the  string is
+described in OpenSSL ciphers(1).
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-k, \-\-insecure
+Don\(aqt  verify   backend  server\(aqs  certificate   if  \fI\%\-p\fP,
+\fI\%\-\-client\fP    or    \fI\%\-\-http2\-bridge\fP     are    given    and
+\fI\%\-\-backend\-no\-tls\fP is not given.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-cacert=<PATH>
+Set path to trusted CA  certificate file if \fI\%\-p\fP, \fI\%\-\-client\fP
+or \fI\%\-\-http2\-bridge\fP are given  and \fI\%\-\-backend\-no\-tls\fP is not
+given.  The file must be  in PEM format.  It can contain
+multiple  certificates.    If  the  linked   OpenSSL  is
+configured to  load system  wide certificates,  they are
+loaded at startup regardless of this option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-private\-key\-passwd\-file=<PATH>
+Path  to file  that contains  password for  the server\(aqs
+private key.   If none is  given and the private  key is
+password protected it\(aqll be requested interactively.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-subcert=<KEYPATH>:<CERTPATH>
+Specify  additional certificate  and  private key  file.
+nghttpx will  choose certificates based on  the hostname
+indicated  by  client  using TLS  SNI  extension.   This
+option can be used multiple times.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-tls\-sni\-field=<HOST>
+Explicitly  set the  content of  the TLS  SNI extension.
+This will default to the backend HOST name.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-dh\-param\-file=<PATH>
+Path to file that contains  DH parameters in PEM format.
+Without  this   option,  DHE   cipher  suites   are  not
+available.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-npn\-list=<LIST>
+Comma delimited list of  ALPN protocol identifier sorted
+in the  order of preference.  That  means most desirable
+protocol comes  first.  This  is used  in both  ALPN and
+NPN.  The parameter must be  delimited by a single comma
+only  and any  white spaces  are  treated as  a part  of
+protocol string.
+.sp
+Default: \fBh2\-16,h2\-14,spdy/3.1,http/1.1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-verify\-client
+Require and verify client certificate.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-verify\-client\-cacert=<PATH>
+Path  to file  that contains  CA certificates  to verify
+client certificate.  The file must be in PEM format.  It
+can contain multiple certificates.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-client\-private\-key\-file=<PATH>
+Path to  file that contains  client private key  used in
+backend client authentication.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-client\-cert\-file=<PATH>
+Path to  file that  contains client certificate  used in
+backend client authentication.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-proto\-list=<LIST>
+Comma delimited list of  SSL/TLS protocol to be enabled.
+The following protocols  are available: TLSv1.2, TLSv1.1
+and   TLSv1.0.    The   name   matching   is   done   in
+case\-insensitive   manner.    The  parameter   must   be
+delimited by  a single comma  only and any  white spaces
+are treated as a part of protocol string.
+.sp
+Default: \fBTLSv1.2,TLSv1.1\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ticket\-key\-file=<PATH>
+Path  to file  that  contains 48  bytes  random data  to
+construct TLS  session ticket parameters.   This options
+can  be  used  repeatedly  to  specify  multiple  ticket
+parameters.  If several files  are given, only the first
+key is used to encrypt  TLS session tickets.  Other keys
+are accepted  but server  will issue new  session ticket
+with  first  key.   This allows  session  key  rotation.
+Please   note  that   key   rotation   does  not   occur
+automatically.   User should  rearrange files  or change
+options  values  and  restart  nghttpx  gracefully.   If
+opening or reading given file fails, all loaded keys are
+discarded and it is treated as if none of this option is
+given.  If this option is not given or an error occurred
+while  opening  or  reading  a file,  key  is  generated
+automatically and  renewed every 12hrs.  At  most 2 keys
+are stored in memory.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-tls\-ctx\-per\-worker
+Create OpenSSL\(aqs SSL_CTX per worker, so that no internal
+locking is required.  This  may improve scalability with
+multi  threaded   configuration.   If  this   option  is
+enabled, session ID is  no longer shared accross SSL_CTX
+objects, which means session  ID generated by one worker
+is not acceptable by another worker.  On the other hand,
+session ticket key is shared across all worker threads.
+.UNINDENT
+.SS HTTP/2 and SPDY
+.INDENT 0.0
+.TP
+.B \-c, \-\-http2\-max\-concurrent\-streams=<N>
+Set the maximum number of  the concurrent streams in one
+HTTP/2 and SPDY session.
+.sp
+Default: \fB100\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-window\-bits=<N>
+Sets the  per\-stream initial window size  of HTTP/2 SPDY
+frontend connection.  For HTTP/2,  the size is 2**<N>\-1.
+For SPDY, the size is 2**<N>.
+.sp
+Default: \fB16\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-connection\-window\-bits=<N>
+Sets the  per\-connection window size of  HTTP/2 and SPDY
+frontend   connection.    For   HTTP/2,  the   size   is
+2**<N>\-1. For SPDY, the size is 2**<N>.
+.sp
+Default: \fB16\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-no\-tls
+Disable SSL/TLS on frontend connections.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-window\-bits=<N>
+Sets  the   initial  window   size  of   HTTP/2  backend
+connection to 2**<N>\-1.
+.sp
+Default: \fB16\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-http2\-connection\-window\-bits=<N>
+Sets the  per\-connection window  size of  HTTP/2 backend
+connection to 2**<N>\-1.
+.sp
+Default: \fB16\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-backend\-no\-tls
+Disable SSL/TLS on backend connections.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-http2\-no\-cookie\-crumbling
+Don\(aqt crumble cookie header field.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-padding=<N>
+Add  at most  <N> bytes  to  a HTTP/2  frame payload  as
+padding.  Specify 0 to  disable padding.  This option is
+meant for debugging purpose  and not intended to enhance
+protocol security.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-server\-push
+Disable  HTTP/2  server  push.    Server  push  is  only
+supported  by default  mode and  HTTP/2 frontend.   SPDY
+frontend does not support server push.
+.UNINDENT
+.SS Mode
+.INDENT 0.0
+.TP
+.B (default mode)
+Accept  HTTP/2,  SPDY  and HTTP/1.1  over  SSL/TLS.   If
+\fI\%\-\-frontend\-no\-tls\fP is  used, accept HTTP/2  and HTTP/1.1.
+The  incoming HTTP/1.1  connection  can  be upgraded  to
+HTTP/2  through  HTTP  Upgrade.   The  protocol  to  the
+backend is HTTP/1.1.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-s, \-\-http2\-proxy
+Like default mode, but enable secure proxy mode.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-http2\-bridge
+Like default  mode, but communicate with  the backend in
+HTTP/2 over SSL/TLS.  Thus  the incoming all connections
+are converted  to HTTP/2  connection and relayed  to the
+backend.  See \fI\%\-\-backend\-http\-proxy\-uri\fP option if you are
+behind  the proxy  and want  to connect  to the  outside
+HTTP/2 proxy.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-client
+Accept  HTTP/2   and  HTTP/1.1  without   SSL/TLS.   The
+incoming HTTP/1.1  connection can be upgraded  to HTTP/2
+connection through  HTTP Upgrade.   The protocol  to the
+backend is HTTP/2.   To use nghttpx as  a forward proxy,
+use \fI\%\-p\fP option instead.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-p, \-\-client\-proxy
+Like \fI\%\-\-client\fP  option, but it also  requires the request
+path from frontend must be an absolute URI, suitable for
+use as a forward proxy.
+.UNINDENT
+.SS Logging
+.INDENT 0.0
+.TP
+.B \-L, \-\-log\-level=<LEVEL>
+Set the severity  level of log output.   <LEVEL> must be
+one of INFO, NOTICE, WARN, ERROR and FATAL.
+.sp
+Default: \fBNOTICE\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-accesslog\-file=<PATH>
+Set path to write access log.  To reopen file, send USR1
+signal to nghttpx.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-accesslog\-syslog
+Send  access log  to syslog.   If this  option is  used,
+\fI\%\-\-accesslog\-file\fP option is ignored.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-accesslog\-format=<FORMAT>
+Specify  format  string  for access  log.   The  default
+format is combined format.   The following variables are
+available:
+.INDENT 7.0
+.IP \(bu 2
+$remote_addr: client IP address.
+.IP \(bu 2
+$time_local: local time in Common Log format.
+.IP \(bu 2
+$time_iso8601: local time in ISO 8601 format.
+.IP \(bu 2
+$request: HTTP request line.
+.IP \(bu 2
+$status: HTTP response status code.
+.IP \(bu 2
+$body_bytes_sent: the  number of bytes sent  to client
+as response body.
+.IP \(bu 2
+$http_<VAR>: value of HTTP  request header <VAR> where
+\(aq_\(aq in <VAR> is replaced with \(aq\-\(aq.
+.IP \(bu 2
+$remote_port: client  port.
+.IP \(bu 2
+$server_port: server port.
+.IP \(bu 2
+$request_time: request processing time in seconds with
+milliseconds resolution.
+.IP \(bu 2
+$pid: PID of the running process.
+.IP \(bu 2
+$alpn: ALPN identifier of the protocol which generates
+the response.   For HTTP/1,  ALPN is  always http/1.1,
+regardless of minor version.
+.UNINDENT
+.sp
+Default: \fB$remote_addr \- \- [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-errorlog\-file=<PATH>
+Set path to write error  log.  To reopen file, send USR1
+signal to nghttpx.
+.sp
+Default: \fB/dev/stderr\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-errorlog\-syslog
+Send  error log  to  syslog.  If  this  option is  used,
+\fI\%\-\-errorlog\-file\fP option is ignored.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-syslog\-facility=<FACILITY>
+Set syslog facility to <FACILITY>.
+.sp
+Default: \fBdaemon\fP
+.UNINDENT
+.SS HTTP
+.INDENT 0.0
+.TP
+.B \-\-add\-x\-forwarded\-for
+Append  X\-Forwarded\-For header  field to  the downstream
+request.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-strip\-incoming\-x\-forwarded\-for
+Strip X\-Forwarded\-For  header field from  inbound client
+requests.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-via
+Don\(aqt append to  Via header field.  If  Via header field
+is received, it is left unaltered.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-location\-rewrite
+Don\(aqt rewrite  location header field  on \fI\%\-\-http2\-bridge\fP,
+\fI\%\-\-client\fP  and  default   mode.   For  \fI\%\-\-http2\-proxy\fP  and
+\fI\%\-\-client\-proxy\fP mode,  location header field will  not be
+altered regardless of this option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-no\-host\-rewrite
+Don\(aqt  rewrite  host  and :authority  header  fields  on
+\fI\%\-\-http2\-bridge\fP,   \fI\%\-\-client\fP   and  default   mode.    For
+\fI\%\-\-http2\-proxy\fP  and  \fI\%\-\-client\-proxy\fP mode,  these  headers
+will not be altered regardless of this option.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
+Specify   protocol  ID,   port,  host   and  origin   of
+alternative service.  <HOST>  and <ORIGIN> are optional.
+They are  advertised in  alt\-svc header field  or HTTP/2
+ALTSVC frame.  This option can be used multiple times to
+specify   multiple   alternative   services.    Example:
+\fI\%\-\-altsvc\fP=h2,443
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-add\-response\-header=<HEADER>
+Specify  additional  header  field to  add  to  response
+header set.   This option just appends  header field and
+won\(aqt replace anything already  set.  This option can be
+used several  times to  specify multiple  header fields.
+Example: \fI\%\-\-add\-response\-header\fP="foo: bar"
+.UNINDENT
+.SS Debug
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-dump\-request\-header=<PATH>
+Dumps request headers received by HTTP/2 frontend to the
+file denoted  in <PATH>.  The  output is done  in HTTP/1
+header field format and each header block is followed by
+an empty line.  This option  is not thread safe and MUST
+NOT be used with option \fI\%\-n\fP<N>, where <N> >= 2.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-frontend\-http2\-dump\-response\-header=<PATH>
+Dumps response headers sent  from HTTP/2 frontend to the
+file denoted  in <PATH>.  The  output is done  in HTTP/1
+header field format and each header block is followed by
+an empty line.  This option  is not thread safe and MUST
+NOT be used with option \fI\%\-n\fP<N>, where <N> >= 2.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-o, \-\-frontend\-frame\-debug
+Print HTTP/2 frames in  frontend to stderr.  This option
+is  not thread  safe and  MUST NOT  be used  with option
+\fI\%\-n\fP=N, where N >= 2.
+.UNINDENT
+.SS Process
+.INDENT 0.0
+.TP
+.B \-D, \-\-daemon
+Run in a background.  If \fI\%\-D\fP is used, the current working
+directory is changed to \(aq\fI/\fP\(aq.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-pid\-file=<PATH>
+Set path to save PID of this program.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-\-user=<USER>
+Run this program as <USER>.   This option is intended to
+be used to drop root privileges.
+.UNINDENT
+.SS Misc
+.INDENT 0.0
+.TP
+.B \-\-conf=<PATH>
+Load configuration from <PATH>.
+.sp
+Default: \fB/etc/nghttpx/nghttpx.conf\fP
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-v, \-\-version
+Print version and exit.
+.UNINDENT
+.INDENT 0.0
+.TP
+.B \-h, \-\-help
+Print this help and exit.
+.UNINDENT
+.sp
+The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+10 * 1024).  Units are K, M and G (powers of 1024).
+.sp
+The <DURATION> argument is an integer and an optional unit (e.g., 1s
+is 1 second and 500ms is 500  milliseconds).  Units are s or ms.  If
+a unit is omitted, a second is used as unit.
+.SH FILES
+.INDENT 0.0
+.TP
+.B \fI/etc/nghttpx/nghttpx.conf\fP
+The default configuration file path nghttpx searches at startup.
+The configuration file path can be changed using \fI\%\-\-conf\fP
+option.
+.sp
+Those lines which are staring \fB#\fP are treated as comment.
+.sp
+The option name in the configuration file is the long command\-line
+option name with leading \fB\-\-\fP stripped (e.g., \fBfrontend\fP).  Put
+\fB=\fP between option name and value.  Don\(aqt put extra leading or
+trailing spaces.
+.sp
+The options which do not take argument in the command\-line \fItake\fP
+argument in the configuration file.  Specify \fByes\fP as an argument
+(e.g., \fBhttp2\-proxy=yes\fP).  If other string is given, it is
+ignored.
+.sp
+To specify private key and certificate file which are given as
+positional arguments in commnad\-line, use \fBprivate\-key\-file\fP and
+\fBcertificate\-file\fP\&.
+.sp
+\fI\%\-\-conf\fP option cannot be used in the configuration file and
+will be ignored if specified.
+.UNINDENT
+.SH SIGNALS
+.INDENT 0.0
+.TP
+.B SIGQUIT
+Shutdown gracefully.  First accept pending connections and stop
+accepting connection.  After all connections are handled, nghttpx
+exits.
+.TP
+.B SIGUSR1
+Reopen log files.
+.TP
+.B SIGUSR2
+Fork and execute nghttpx.  It will execute the binary in the same
+path with same command\-line arguments and environment variables.
+After new process comes up, sending SIGQUIT to the original process
+to perform hot swapping.
+.UNINDENT
+.SH SERVER PUSH
+.sp
+nghttpx supports HTTP/2 server push in default mode.  nghttpx looks
+for Link header field (\fI\%RFC 5988\fP) in response headers for
+backend server and extracts URI\-reference with parameter
+\fBrel=preload\fP (see \fI\%preload\fP)
+and pushes those URIs to the frontend client. Here is a sample Link
+header field to initiate server push:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+Link: </fonts/font.woff>; rel=preload
+Link: </css/theme.css>; rel=preload
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.sp
+Currently, the following restrictions are applied for server push:
+.INDENT 0.0
+.IP 1. 3
+URI\-reference must not contain authority.  If it exists, it is not
+pushed.  \fB/fonts/font.woff\fP and \fBcss/theme.css\fP are eligible to
+be pushed.  \fBhttps://example.org/fonts/font.woff\fP and
+\fB//example.org/css/theme.css\fP are not.
+.IP 2. 3
+The associated stream must have method "GET" or "POST".  The
+associated stream\(aqs status code must be 200.
+.UNINDENT
+.sp
+These limitations may be loosened in the future release.
+.SH SEE ALSO
+.sp
+\fInghttp(1)\fP, \fInghttpd(1)\fP, \fIh2load(1)\fP
+.SH AUTHOR
+Tatsuhiro Tsujikawa
+.SH COPYRIGHT
+2012, 2015, Tatsuhiro Tsujikawa
+.\" Generated by docutils manpage writer.
+.
diff --git a/doc/nghttpx.1.rst b/doc/nghttpx.1.rst
new file mode 100644 (file)
index 0000000..eff5751
--- /dev/null
@@ -0,0 +1,749 @@
+
+nghttpx(1)
+==========
+
+SYNOPSIS
+--------
+
+**nghttpx** [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+
+DESCRIPTION
+-----------
+
+A reverse proxy for HTTP/2, HTTP/1 and SPDY.
+
+.. describe:: <PRIVATE_KEY>
+
+    
+    Set path  to server's private key.   Required unless :option:`-p`\,
+    :option:`--client` or :option:`\--frontend-no-tls` are given.
+
+.. describe:: <CERT>
+
+    Set path  to server's certificate.  Required  unless :option:`-p`\,
+    :option:`--client` or :option:`\--frontend-no-tls` are given.
+
+
+OPTIONS
+-------
+
+The options are categorized into several groups.
+
+Connections
+~~~~~~~~~~~
+
+.. option:: -b, --backend=<HOST,PORT>
+
+    Set backend host and port.  For HTTP/1 backend, multiple
+    backend addresses are accepted by repeating this option.
+    HTTP/2  backend   does  not  support   multiple  backend
+    addresses  and the  first occurrence  of this  option is
+    used.
+
+    Default: ``127.0.0.1,80``
+
+.. option:: -f, --frontend=<HOST,PORT>
+
+    Set  frontend  host and  port.   If  <HOST> is  '\*',  it
+    assumes all addresses including both IPv4 and IPv6.
+
+    Default: ``*,3000``
+
+.. option:: --backlog=<N>
+
+    Set listen backlog size.
+
+    Default: ``512``
+
+.. option:: --backend-ipv4
+
+    Resolve backend hostname to IPv4 address only.
+
+.. option:: --backend-ipv6
+
+    Resolve backend hostname to IPv6 address only.
+
+.. option:: --backend-http-proxy-uri=<URI>
+
+    Specify      proxy       URI      in       the      form
+    http://[<USER>:<PASS>@]<PROXY>:<PORT>.    If   a   proxy
+    requires  authentication,  specify  <USER>  and  <PASS>.
+    Note that  they must be properly  percent-encoded.  This
+    proxy  is used  when the  backend connection  is HTTP/2.
+    First,  make  a CONNECT  request  to  the proxy  and  it
+    connects  to the  backend  on behalf  of nghttpx.   This
+    forms  tunnel.   After  that, nghttpx  performs  SSL/TLS
+    handshake with  the downstream through the  tunnel.  The
+    timeouts when connecting and  making CONNECT request can
+    be     specified    by     :option:`--backend-read-timeout`    and
+    :option:`--backend-write-timeout` options.
+
+
+Performance
+~~~~~~~~~~~
+
+.. option:: -n, --workers=<N>
+
+    Set the number of worker threads.
+
+    Default: ``1``
+
+.. option:: --read-rate=<SIZE>
+
+    Set maximum  average read  rate on  frontend connection.
+    Setting 0 to this option means read rate is unlimited.
+
+    Default: ``0``
+
+.. option:: --read-burst=<SIZE>
+
+    Set  maximum read  burst  size  on frontend  connection.
+    Setting  0  to this  option  means  read burst  size  is
+    unlimited.
+
+    Default: ``0``
+
+.. option:: --write-rate=<SIZE>
+
+    Set maximum  average write rate on  frontend connection.
+    Setting 0 to this option means write rate is unlimited.
+
+    Default: ``0``
+
+.. option:: --write-burst=<SIZE>
+
+    Set  maximum write  burst size  on frontend  connection.
+    Setting  0 to  this  option means  write  burst size  is
+    unlimited.
+
+    Default: ``0``
+
+.. option:: --worker-read-rate=<SIZE>
+
+    Set maximum average read rate on frontend connection per
+    worker.  Setting  0 to  this option  means read  rate is
+    unlimited.  Not implemented yet.
+
+    Default: ``0``
+
+.. option:: --worker-read-burst=<SIZE>
+
+    Set maximum  read burst size on  frontend connection per
+    worker.  Setting 0 to this  option means read burst size
+    is unlimited.  Not implemented yet.
+
+    Default: ``0``
+
+.. option:: --worker-write-rate=<SIZE>
+
+    Set maximum  average write  rate on  frontend connection
+    per worker.  Setting  0 to this option  means write rate
+    is unlimited.  Not implemented yet.
+
+    Default: ``0``
+
+.. option:: --worker-write-burst=<SIZE>
+
+    Set maximum write burst  size on frontend connection per
+    worker.  Setting 0 to this option means write burst size
+    is unlimited.  Not implemented yet.
+
+    Default: ``0``
+
+.. option:: --worker-frontend-connections=<N>
+
+    Set maximum number  of simultaneous connections frontend
+    accepts.  Setting 0 means unlimited.
+
+    Default: ``0``
+
+.. option:: --backend-http1-connections-per-host=<N>
+
+    Set   maximum  number   of  backend   concurrent  HTTP/1
+    connections per host.  This option is meaningful when :option:`-s`
+    option is used.  To limit  the number of connections per
+    frontend        for       default        mode,       use
+    :option:`--backend-http1-connections-per-frontend`\.
+
+    Default: ``8``
+
+.. option:: --backend-http1-connections-per-frontend=<N>
+
+    Set   maximum  number   of  backend   concurrent  HTTP/1
+    connections per frontend.  This  option is only used for
+    default mode.   0 means unlimited.  To  limit the number
+    of connections  per host for  HTTP/2 or SPDY  proxy mode
+    (-s option), use :option:`--backend-http1-connections-per-host`\.
+
+    Default: ``0``
+
+.. option:: --rlimit-nofile=<N>
+
+    Set maximum number of open files (RLIMIT_NOFILE) to <N>.
+    If 0 is given, nghttpx does not set the limit.
+
+    Default: ``0``
+
+.. option:: --backend-request-buffer=<SIZE>
+
+    Set buffer size used to store backend request.
+
+    Default: ``16K``
+
+.. option:: --backend-response-buffer=<SIZE>
+
+    Set buffer size used to store backend response.
+
+    Default: ``16K``
+
+
+Timeout
+~~~~~~~
+
+.. option:: --frontend-http2-read-timeout=<DURATION>
+
+    Specify  read  timeout  for  HTTP/2  and  SPDY  frontend
+    connection.
+
+    Default: ``180s``
+
+.. option:: --frontend-read-timeout=<DURATION>
+
+    Specify read timeout for HTTP/1.1 frontend connection.
+
+    Default: ``180s``
+
+.. option:: --frontend-write-timeout=<DURATION>
+
+    Specify write timeout for all frontend connections.
+
+    Default: ``30s``
+
+.. option:: --stream-read-timeout=<DURATION>
+
+    Specify  read timeout  for HTTP/2  and SPDY  streams.  0
+    means no timeout.
+
+    Default: ``0``
+
+.. option:: --stream-write-timeout=<DURATION>
+
+    Specify write  timeout for  HTTP/2 and SPDY  streams.  0
+    means no timeout.
+
+    Default: ``0``
+
+.. option:: --backend-read-timeout=<DURATION>
+
+    Specify read timeout for backend connection.
+
+    Default: ``180s``
+
+.. option:: --backend-write-timeout=<DURATION>
+
+    Specify write timeout for backend connection.
+
+    Default: ``30s``
+
+.. option:: --backend-keep-alive-timeout=<DURATION>
+
+    Specify keep-alive timeout for backend connection.
+
+    Default: ``2s``
+
+.. option:: --listener-disable-timeout=<DURATION>
+
+    After accepting  connection failed,  connection listener
+    is disabled  for a given  amount of time.   Specifying 0
+    disables this feature.
+
+    Default: ``0``
+
+
+SSL/TLS
+~~~~~~~
+
+.. option:: --ciphers=<SUITE>
+
+    Set allowed  cipher list.  The  format of the  string is
+    described in OpenSSL ciphers(1).
+
+.. option:: -k, --insecure
+
+    Don't  verify   backend  server's  certificate   if  :option:`-p`\,
+    :option:`--client`    or    :option:`\--http2-bridge`     are    given    and
+    :option:`--backend-no-tls` is not given.
+
+.. option:: --cacert=<PATH>
+
+    Set path to trusted CA  certificate file if :option:`-p`\, :option:`--client`
+    or :option:`--http2-bridge` are given  and :option:`\--backend-no-tls` is not
+    given.  The file must be  in PEM format.  It can contain
+    multiple  certificates.    If  the  linked   OpenSSL  is
+    configured to  load system  wide certificates,  they are
+    loaded at startup regardless of this option.
+
+.. option:: --private-key-passwd-file=<PATH>
+
+    Path  to file  that contains  password for  the server's
+    private key.   If none is  given and the private  key is
+    password protected it'll be requested interactively.
+
+.. option:: --subcert=<KEYPATH>:<CERTPATH>
+
+    Specify  additional certificate  and  private key  file.
+    nghttpx will  choose certificates based on  the hostname
+    indicated  by  client  using TLS  SNI  extension.   This
+    option can be used multiple times.
+
+.. option:: --backend-tls-sni-field=<HOST>
+
+    Explicitly  set the  content of  the TLS  SNI extension.
+    This will default to the backend HOST name.
+
+.. option:: --dh-param-file=<PATH>
+
+    Path to file that contains  DH parameters in PEM format.
+    Without  this   option,  DHE   cipher  suites   are  not
+    available.
+
+.. option:: --npn-list=<LIST>
+
+    Comma delimited list of  ALPN protocol identifier sorted
+    in the  order of preference.  That  means most desirable
+    protocol comes  first.  This  is used  in both  ALPN and
+    NPN.  The parameter must be  delimited by a single comma
+    only  and any  white spaces  are  treated as  a part  of
+    protocol string.
+
+    Default: ``h2-16,h2-14,spdy/3.1,http/1.1``
+
+.. option:: --verify-client
+
+    Require and verify client certificate.
+
+.. option:: --verify-client-cacert=<PATH>
+
+    Path  to file  that contains  CA certificates  to verify
+    client certificate.  The file must be in PEM format.  It
+    can contain multiple certificates.
+
+.. option:: --client-private-key-file=<PATH>
+
+    Path to  file that contains  client private key  used in
+    backend client authentication.
+
+.. option:: --client-cert-file=<PATH>
+
+    Path to  file that  contains client certificate  used in
+    backend client authentication.
+
+.. option:: --tls-proto-list=<LIST>
+
+    Comma delimited list of  SSL/TLS protocol to be enabled.
+    The following protocols  are available: TLSv1.2, TLSv1.1
+    and   TLSv1.0.    The   name   matching   is   done   in
+    case-insensitive   manner.    The  parameter   must   be
+    delimited by  a single comma  only and any  white spaces
+    are treated as a part of protocol string.
+
+    Default: ``TLSv1.2,TLSv1.1``
+
+.. option:: --tls-ticket-key-file=<PATH>
+
+    Path  to file  that  contains 48  bytes  random data  to
+    construct TLS  session ticket parameters.   This options
+    can  be  used  repeatedly  to  specify  multiple  ticket
+    parameters.  If several files  are given, only the first
+    key is used to encrypt  TLS session tickets.  Other keys
+    are accepted  but server  will issue new  session ticket
+    with  first  key.   This allows  session  key  rotation.
+    Please   note  that   key   rotation   does  not   occur
+    automatically.   User should  rearrange files  or change
+    options  values  and  restart  nghttpx  gracefully.   If
+    opening or reading given file fails, all loaded keys are
+    discarded and it is treated as if none of this option is
+    given.  If this option is not given or an error occurred
+    while  opening  or  reading  a file,  key  is  generated
+    automatically and  renewed every 12hrs.  At  most 2 keys
+    are stored in memory.
+
+.. option:: --tls-ctx-per-worker
+
+    Create OpenSSL's SSL_CTX per worker, so that no internal
+    locking is required.  This  may improve scalability with
+    multi  threaded   configuration.   If  this   option  is
+    enabled, session ID is  no longer shared accross SSL_CTX
+    objects, which means session  ID generated by one worker
+    is not acceptable by another worker.  On the other hand,
+    session ticket key is shared across all worker threads.
+
+
+HTTP/2 and SPDY
+~~~~~~~~~~~~~~~
+
+.. option:: -c, --http2-max-concurrent-streams=<N>
+
+    Set the maximum number of  the concurrent streams in one
+    HTTP/2 and SPDY session.
+
+    Default: ``100``
+
+.. option:: --frontend-http2-window-bits=<N>
+
+    Sets the  per-stream initial window size  of HTTP/2 SPDY
+    frontend connection.  For HTTP/2,  the size is 2\*\*<N>-1.
+    For SPDY, the size is 2\*\*<N>.
+
+    Default: ``16``
+
+.. option:: --frontend-http2-connection-window-bits=<N>
+
+    Sets the  per-connection window size of  HTTP/2 and SPDY
+    frontend   connection.    For   HTTP/2,  the   size   is
+    2**<N>-1. For SPDY, the size is 2\*\*<N>.
+
+    Default: ``16``
+
+.. option:: --frontend-no-tls
+
+    Disable SSL/TLS on frontend connections.
+
+.. option:: --backend-http2-window-bits=<N>
+
+    Sets  the   initial  window   size  of   HTTP/2  backend
+    connection to 2\*\*<N>-1.
+
+    Default: ``16``
+
+.. option:: --backend-http2-connection-window-bits=<N>
+
+    Sets the  per-connection window  size of  HTTP/2 backend
+    connection to 2\*\*<N>-1.
+
+    Default: ``16``
+
+.. option:: --backend-no-tls
+
+    Disable SSL/TLS on backend connections.
+
+.. option:: --http2-no-cookie-crumbling
+
+    Don't crumble cookie header field.
+
+.. option:: --padding=<N>
+
+    Add  at most  <N> bytes  to  a HTTP/2  frame payload  as
+    padding.  Specify 0 to  disable padding.  This option is
+    meant for debugging purpose  and not intended to enhance
+    protocol security.
+
+.. option:: --no-server-push
+
+    Disable  HTTP/2  server  push.    Server  push  is  only
+    supported  by default  mode and  HTTP/2 frontend.   SPDY
+    frontend does not support server push.
+
+
+Mode
+~~~~
+
+.. describe:: (default mode)
+
+    
+    Accept  HTTP/2,  SPDY  and HTTP/1.1  over  SSL/TLS.   If
+    :option:`--frontend-no-tls` is  used, accept HTTP/2  and HTTP/1.1.
+    The  incoming HTTP/1.1  connection  can  be upgraded  to
+    HTTP/2  through  HTTP  Upgrade.   The  protocol  to  the
+    backend is HTTP/1.1.
+
+.. option:: -s, --http2-proxy
+
+    Like default mode, but enable secure proxy mode.
+
+.. option:: --http2-bridge
+
+    Like default  mode, but communicate with  the backend in
+    HTTP/2 over SSL/TLS.  Thus  the incoming all connections
+    are converted  to HTTP/2  connection and relayed  to the
+    backend.  See :option:`--backend-http-proxy-uri` option if you are
+    behind  the proxy  and want  to connect  to the  outside
+    HTTP/2 proxy.
+
+.. option:: --client
+
+    Accept  HTTP/2   and  HTTP/1.1  without   SSL/TLS.   The
+    incoming HTTP/1.1  connection can be upgraded  to HTTP/2
+    connection through  HTTP Upgrade.   The protocol  to the
+    backend is HTTP/2.   To use nghttpx as  a forward proxy,
+    use :option:`-p` option instead.
+
+.. option:: -p, --client-proxy
+
+    Like :option:`--client`  option, but it also  requires the request
+    path from frontend must be an absolute URI, suitable for
+    use as a forward proxy.
+
+
+Logging
+~~~~~~~
+
+.. option:: -L, --log-level=<LEVEL>
+
+    Set the severity  level of log output.   <LEVEL> must be
+    one of INFO, NOTICE, WARN, ERROR and FATAL.
+
+    Default: ``NOTICE``
+
+.. option:: --accesslog-file=<PATH>
+
+    Set path to write access log.  To reopen file, send USR1
+    signal to nghttpx.
+
+.. option:: --accesslog-syslog
+
+    Send  access log  to syslog.   If this  option is  used,
+    :option:`--accesslog-file` option is ignored.
+
+.. option:: --accesslog-format=<FORMAT>
+
+    Specify  format  string  for access  log.   The  default
+    format is combined format.   The following variables are
+    available:
+
+    * $remote_addr: client IP address.
+    * $time_local: local time in Common Log format.
+    * $time_iso8601: local time in ISO 8601 format.
+    * $request: HTTP request line.
+    * $status: HTTP response status code.
+    * $body_bytes_sent: the  number of bytes sent  to client
+      as response body.
+    * $http_<VAR>: value of HTTP  request header <VAR> where
+      '_' in <VAR> is replaced with '-'.
+    * $remote_port: client  port.
+    * $server_port: server port.
+    * $request_time: request processing time in seconds with
+      milliseconds resolution.
+    * $pid: PID of the running process.
+    * $alpn: ALPN identifier of the protocol which generates
+      the response.   For HTTP/1,  ALPN is  always http/1.1,
+      regardless of minor version.
+
+
+    Default: ``$remote_addr - - [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"``
+
+.. option:: --errorlog-file=<PATH>
+
+    Set path to write error  log.  To reopen file, send USR1
+    signal to nghttpx.
+
+    Default: ``/dev/stderr``
+
+.. option:: --errorlog-syslog
+
+    Send  error log  to  syslog.  If  this  option is  used,
+    :option:`--errorlog-file` option is ignored.
+
+.. option:: --syslog-facility=<FACILITY>
+
+    Set syslog facility to <FACILITY>.
+
+    Default: ``daemon``
+
+
+HTTP
+~~~~
+
+.. option:: --add-x-forwarded-for
+
+    Append  X-Forwarded-For header  field to  the downstream
+    request.
+
+.. option:: --strip-incoming-x-forwarded-for
+
+    Strip X-Forwarded-For  header field from  inbound client
+    requests.
+
+.. option:: --no-via
+
+    Don't append to  Via header field.  If  Via header field
+    is received, it is left unaltered.
+
+.. option:: --no-location-rewrite
+
+    Don't rewrite  location header field  on :option:`--http2-bridge`\,
+    :option:`--client`  and  default   mode.   For  :option:`\--http2-proxy`  and
+    :option:`--client-proxy` mode,  location header field will  not be
+    altered regardless of this option.
+
+.. option:: --no-host-rewrite
+
+    Don't  rewrite  host  and :authority  header  fields  on
+    :option:`--http2-bridge`\,   :option:`--client`   and  default   mode.    For
+    :option:`--http2-proxy`  and  :option:`\--client-proxy` mode,  these  headers
+    will not be altered regardless of this option.
+
+.. option:: --altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
+
+    Specify   protocol  ID,   port,  host   and  origin   of
+    alternative service.  <HOST>  and <ORIGIN> are optional.
+    They are  advertised in  alt-svc header field  or HTTP/2
+    ALTSVC frame.  This option can be used multiple times to
+    specify   multiple   alternative   services.    Example:
+    :option:`--altsvc`\=h2,443
+
+.. option:: --add-response-header=<HEADER>
+
+    Specify  additional  header  field to  add  to  response
+    header set.   This option just appends  header field and
+    won't replace anything already  set.  This option can be
+    used several  times to  specify multiple  header fields.
+    Example: :option:`--add-response-header`\="foo: bar"
+
+
+Debug
+~~~~~
+
+.. option:: --frontend-http2-dump-request-header=<PATH>
+
+    Dumps request headers received by HTTP/2 frontend to the
+    file denoted  in <PATH>.  The  output is done  in HTTP/1
+    header field format and each header block is followed by
+    an empty line.  This option  is not thread safe and MUST
+    NOT be used with option :option:`-n`\<N>, where <N> >= 2.
+
+.. option:: --frontend-http2-dump-response-header=<PATH>
+
+    Dumps response headers sent  from HTTP/2 frontend to the
+    file denoted  in <PATH>.  The  output is done  in HTTP/1
+    header field format and each header block is followed by
+    an empty line.  This option  is not thread safe and MUST
+    NOT be used with option :option:`-n`\<N>, where <N> >= 2.
+
+.. option:: -o, --frontend-frame-debug
+
+    Print HTTP/2 frames in  frontend to stderr.  This option
+    is  not thread  safe and  MUST NOT  be used  with option
+    :option:`-n`\=N, where N >= 2.
+
+
+Process
+~~~~~~~
+
+.. option:: -D, --daemon
+
+    Run in a background.  If :option:`-D` is used, the current working
+    directory is changed to '*/*'.
+
+.. option:: --pid-file=<PATH>
+
+    Set path to save PID of this program.
+
+.. option:: --user=<USER>
+
+    Run this program as <USER>.   This option is intended to
+    be used to drop root privileges.
+
+
+Misc
+~~~~
+
+.. option:: --conf=<PATH>
+
+    Load configuration from <PATH>.
+
+    Default: ``/etc/nghttpx/nghttpx.conf``
+
+.. option:: -v, --version
+
+    Print version and exit.
+
+.. option:: -h, --help
+
+    Print this help and exit.
+
+
+The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+10 * 1024).  Units are K, M and G (powers of 1024).
+
+The <DURATION> argument is an integer and an optional unit (e.g., 1s
+is 1 second and 500ms is 500  milliseconds).  Units are s or ms.  If
+a unit is omitted, a second is used as unit.
+
+FILES
+-----
+
+*/etc/nghttpx/nghttpx.conf*
+  The default configuration file path nghttpx searches at startup.
+  The configuration file path can be changed using :option:`--conf`
+  option.
+
+  Those lines which are staring ``#`` are treated as comment.
+
+  The option name in the configuration file is the long command-line
+  option name with leading ``--`` stripped (e.g., ``frontend``).  Put
+  ``=`` between option name and value.  Don't put extra leading or
+  trailing spaces.
+
+  The options which do not take argument in the command-line *take*
+  argument in the configuration file.  Specify ``yes`` as an argument
+  (e.g., ``http2-proxy=yes``).  If other string is given, it is
+  ignored.
+
+  To specify private key and certificate file which are given as
+  positional arguments in commnad-line, use ``private-key-file`` and
+  ``certificate-file``.
+
+  :option:`--conf` option cannot be used in the configuration file and
+  will be ignored if specified.
+
+SIGNALS
+-------
+
+SIGQUIT
+  Shutdown gracefully.  First accept pending connections and stop
+  accepting connection.  After all connections are handled, nghttpx
+  exits.
+
+SIGUSR1
+  Reopen log files.
+
+SIGUSR2
+  Fork and execute nghttpx.  It will execute the binary in the same
+  path with same command-line arguments and environment variables.
+  After new process comes up, sending SIGQUIT to the original process
+  to perform hot swapping.
+
+SERVER PUSH
+-----------
+
+nghttpx supports HTTP/2 server push in default mode.  nghttpx looks
+for Link header field (`RFC 5988
+<http://tools.ietf.org/html/rfc5988>`_) in response headers for
+backend server and extracts URI-reference with parameter
+``rel=preload`` (see `preload
+<http://w3c.github.io/preload/#interoperability-with-http-link-header>`_)
+and pushes those URIs to the frontend client. Here is a sample Link
+header field to initiate server push:
+
+.. code-block:: http
+
+  Link: </fonts/font.woff>; rel=preload
+  Link: </css/theme.css>; rel=preload
+
+Currently, the following restrictions are applied for server push:
+
+1. URI-reference must not contain authority.  If it exists, it is not
+   pushed.  ``/fonts/font.woff`` and ``css/theme.css`` are eligible to
+   be pushed.  ``https://example.org/fonts/font.woff`` and
+   ``//example.org/css/theme.css`` are not.
+
+2. The associated stream must have method "GET" or "POST".  The
+   associated stream's status code must be 200.
+
+These limitations may be loosened in the future release.
+
+SEE ALSO
+--------
+
+:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`h2load(1)`
diff --git a/doc/nghttpx.h2r b/doc/nghttpx.h2r
new file mode 100644 (file)
index 0000000..e5766db
--- /dev/null
@@ -0,0 +1,77 @@
+FILES
+-----
+
+*/etc/nghttpx/nghttpx.conf*
+  The default configuration file path nghttpx searches at startup.
+  The configuration file path can be changed using :option:`--conf`
+  option.
+
+  Those lines which are staring ``#`` are treated as comment.
+
+  The option name in the configuration file is the long command-line
+  option name with leading ``--`` stripped (e.g., ``frontend``).  Put
+  ``=`` between option name and value.  Don't put extra leading or
+  trailing spaces.
+
+  The options which do not take argument in the command-line *take*
+  argument in the configuration file.  Specify ``yes`` as an argument
+  (e.g., ``http2-proxy=yes``).  If other string is given, it is
+  ignored.
+
+  To specify private key and certificate file which are given as
+  positional arguments in commnad-line, use ``private-key-file`` and
+  ``certificate-file``.
+
+  :option:`--conf` option cannot be used in the configuration file and
+  will be ignored if specified.
+
+SIGNALS
+-------
+
+SIGQUIT
+  Shutdown gracefully.  First accept pending connections and stop
+  accepting connection.  After all connections are handled, nghttpx
+  exits.
+
+SIGUSR1
+  Reopen log files.
+
+SIGUSR2
+  Fork and execute nghttpx.  It will execute the binary in the same
+  path with same command-line arguments and environment variables.
+  After new process comes up, sending SIGQUIT to the original process
+  to perform hot swapping.
+
+SERVER PUSH
+-----------
+
+nghttpx supports HTTP/2 server push in default mode.  nghttpx looks
+for Link header field (`RFC 5988
+<http://tools.ietf.org/html/rfc5988>`_) in response headers for
+backend server and extracts URI-reference with parameter
+``rel=preload`` (see `preload
+<http://w3c.github.io/preload/#interoperability-with-http-link-header>`_)
+and pushes those URIs to the frontend client. Here is a sample Link
+header field to initiate server push:
+
+.. code-block:: http
+
+  Link: </fonts/font.woff>; rel=preload
+  Link: </css/theme.css>; rel=preload
+
+Currently, the following restrictions are applied for server push:
+
+1. URI-reference must not contain authority.  If it exists, it is not
+   pushed.  ``/fonts/font.woff`` and ``css/theme.css`` are eligible to
+   be pushed.  ``https://example.org/fonts/font.woff`` and
+   ``//example.org/css/theme.css`` are not.
+
+2. The associated stream must have method "GET" or "POST".  The
+   associated stream's status code must be 200.
+
+These limitations may be loosened in the future release.
+
+SEE ALSO
+--------
+
+:manpage:`nghttp(1)`, :manpage:`nghttpd(1)`, :manpage:`h2load(1)`
diff --git a/doc/package_README.rst.in b/doc/package_README.rst.in
new file mode 100644 (file)
index 0000000..dfa6b2d
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/README.rst
diff --git a/doc/python-apiref.rst.in b/doc/python-apiref.rst.in
new file mode 100644 (file)
index 0000000..5fd40de
--- /dev/null
@@ -0,0 +1 @@
+.. include:: @top_srcdir@/doc/sources/python-apiref.rst
diff --git a/doc/sources/building-android-binary.rst b/doc/sources/building-android-binary.rst
new file mode 100644 (file)
index 0000000..99aec39
--- /dev/null
@@ -0,0 +1,135 @@
+Building Android binary
+=======================
+
+In this article, we briefly describe how to build Android binary using
+`Android NDK <http://developer.android.com/tools/sdk/ndk/index.html>`_
+cross-compiler on Debian Linux.
+
+The easiest way to build android binary is use Dockerfile.android.
+See Dockerfile.android for more details.  If you cannot use
+Dockerfile.android for whatever reason, continue to read the rest of
+this article.
+
+We offer ``android-config`` and ``android-make`` scripts to make the
+build easier.  To make these script work, NDK toolchain must be
+installed in the following way.  First, let us introduce
+``ANDROID_HOME`` environment variable.  We need to install toolchain
+under ``$ANDROID_HOME/toolchain``.  An user can freely choose the path
+for ``ANDROID_HOME``.  For example, to install toolchain under
+``$ANDROID_HOME/toolchain``, do this in the the directory where NDK is
+unpacked::
+
+    $ build/tools/make-standalone-toolchain.sh \
+      --install-dir=$ANDROID_HOME/toolchain \
+      --toolchain=arm-linux-androideabi-4.9 \
+      --llvm-version=3.5 \
+      --platform=android-16
+
+The additional flag ``--system=linux-x86_64`` may be required if you
+are using x86_64 system.
+
+The platform level is not important here because we don't use Android
+specific C/C++ API.
+
+The dependent libraries, such as OpenSSL and libev should be built
+with the toolchain and installed under ``$ANDROID_HOME/usr/local``.
+We recommend to build these libraries as static library to make the
+deployment easier.  libxml2 support is currently disabled.
+
+We use zlib which comes with Android NDK, so we don't have to build it
+by ourselves.
+
+If SPDY support is required for nghttpx and h2load, build and install
+spdylay as well.
+
+Before running ``android-config`` and ``android-make``,
+``ANDROID_HOME`` environment variable must be set to point to the
+correct path.  Also add ``$ANDROID_HOME/toolchain/bin`` to ``PATH``::
+
+    $ export PATH=$PATH:$ANDROID_HOME/toolchain/bin
+
+To configure OpenSSL, use the following script:
+
+.. code-block:: sh
+
+    #!/bin/sh
+
+    if [ -z "$ANDROID_HOME" ]; then
+        echo 'No $ANDROID_HOME specified.'
+        exit 1
+    fi
+    PREFIX=$ANDROID_HOME/usr/local
+    TOOLCHAIN=$ANDROID_HOME/toolchain
+    PATH=$TOOLCHAIN/bin:$PATH
+
+    export CROSS_COMPILE=$TOOLCHAIN/bin/arm-linux-androideabi-
+    ./Configure --prefix=$PREFIX android
+
+And run ``make install`` to build and install.
+
+We cannot compile libev without modification.  Apply `this patch
+<https://gist.github.com/tatsuhiro-t/48c45f08950f587180ed>`_ before
+configuring libev.  This patch is for libev-4.19.  After applying the
+patch, to configure libev, use the following script:
+
+.. code-block:: sh
+
+    #!/bin/sh
+
+    if [ -z "$ANDROID_HOME" ]; then
+        echo 'No $ANDROID_HOME specified.'
+        exit 1
+    fi
+    PREFIX=$ANDROID_HOME/usr/local
+    TOOLCHAIN=$ANDROID_HOME/toolchain
+    PATH=$TOOLCHAIN/bin:$PATH
+
+    ./configure \
+        --host=arm-linux-androideabi \
+        --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+        --prefix=$PREFIX \
+        --disable-shared \
+        --enable-static \
+        CPPFLAGS=-I$PREFIX/include \
+        LDFLAGS=-L$PREFIX/lib
+
+And run ``make install`` to build and install.
+
+To configure spdylay, use the following script:
+
+.. code-block:: sh
+
+    if [ -z "$ANDROID_HOME" ]; then
+       echo 'No $ANDROID_HOME specified.'
+       exit 1
+    fi
+    PREFIX=$ANDROID_HOME/usr/local
+    TOOLCHAIN=$ANDROID_HOME/toolchain
+    PATH=$TOOLCHAIN/bin:$PATH
+
+    ./configure \
+       --disable-shared \
+       --host=arm-linux-androideabi \
+       --build=`dpkg-architecture -qDEB_BUILD_GNU_TYPE` \
+       --prefix=$PREFIX \
+       --without-libxml2 \
+       --disable-src \
+       --disable-examples \
+       CPPFLAGS="-I$PREFIX/include" \
+       PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig" \
+       LDFLAGS="-L$PREFIX/lib"
+
+And run ``make install`` to build and install.  After spdylay
+installation, edit $ANDROID_HOME/usr/local/lib/pkgconfig/libspdylay.pc
+and remove the following line::
+
+    Requires.private: zlib
+
+After prerequisite libraries are prepared, run ``android-config`` and
+then ``android-make`` to compile nghttp2 source files.
+
+If all went well, application binaries, such as nghttpx, are created
+under src directory.  Strip debugging information from the binary
+using the following command::
+
+    $ arm-linux-androideabi-strip src/nghttpx
diff --git a/doc/sources/contribute.rst b/doc/sources/contribute.rst
new file mode 100644 (file)
index 0000000..14a11e2
--- /dev/null
@@ -0,0 +1,57 @@
+Contribution Guidelines
+=======================
+
+[This text was composed based on 1.2. License section of curl/libcurl
+project.]
+
+When contributing with code, you agree to put your changes and new
+code under the same license nghttp2 is already using unless stated and
+agreed otherwise.
+
+When changing existing source code, you do not alter the copyright of
+the original file(s).  The copyright will still be owned by the
+original creator(s) or those who have been assigned copyright by the
+original author(s).
+
+By submitting a patch to the nghttp2 project, you are assumed to have
+the right to the code and to be allowed by your employer or whatever
+to hand over that patch/code to us.  We will credit you for your
+changes as far as possible, to give credit but also to keep a trace
+back to who made what changes.  Please always provide us with your
+full real name when contributing!
+
+Coding style
+------------
+
+We use clang-format to format source code consistently.  The
+clang-format configuration file .clang-format is located at the root
+directory.  Since clang-format produces slightly different results
+between versions, we currently use clang-format which comes with
+clang-3.5.
+
+To detect any violation to the coding style, we recommend to setup git
+pre-commit hook to check coding style of the changes you introduced.
+The pre-commit file is located at the root directory.  Copy it under
+.git/hooks and make sure that it is executable.  The pre-commit script
+uses clang-format-diff.py to detect any style errors.  If it is not in
+your PATH or it exists under different name (e.g.,
+clang-format-diff-3.5 in debian), either add it to PATH variable or
+add git option ``clangformatdiff.binary`` to point to the script.
+
+For emacs users, integrating clang-format to emacs is very easy.
+clang-format.el should come with clang distribution.  If it is not
+found, download it from `here
+<https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/clang-format.el>`_.
+And add these lines to your .emacs file:
+
+.. code-block:: lisp
+
+    ;; From
+    ;; https://code.google.com/p/chromium/wiki/Emacs#Use_Google's_C++_style!
+    (load "/<path/to>/clang-format.el")
+    (add-hook 'c-mode-common-hook
+         (function (lambda () (local-set-key (kbd "TAB")
+                                             'clang-format-region))))
+
+You can find other editor integration in
+http://clang.llvm.org/docs/ClangFormat.html.
diff --git a/doc/sources/h2load-howto.rst b/doc/sources/h2load-howto.rst
new file mode 100644 (file)
index 0000000..698c3e7
--- /dev/null
@@ -0,0 +1,90 @@
+h2load - HTTP/2 benchmarking tool - HOW-TO
+==========================================
+
+h2load is benchmarking tool for HTTP/2.  If built with
+spdylay (http://tatsuhiro-t.github.io/spdylay/) library, it also
+supports SPDY protocol.  It supports SSL/TLS and clear text for both
+HTTP/2 and SPDY.
+
+Basic Usage
+-----------
+
+In order to set benchmark settings, specify following 3 options.
+
+``-n``
+    The number of total requests.  Default: 1
+
+``-c``
+    The number of concurrent clients.  Default: 1
+
+``-m``
+   The max concurrent streams to issue per client.
+   If ``auto`` is given, the number of given URIs is used.
+   Default: ``auto``
+
+Here is a command-line to perform benchmark to URI \https://localhost
+using total 100000 requests, 100 concurrent clients and 10 max
+concurrent streams::
+
+    $ h2load -n100000 -c100 -m10 https://localhost
+
+The benchmarking result looks like this::
+
+    finished in 0 sec, 385 millisec and 851 microsec, 2591 req/s, 1689 kbytes/s
+    requests: 1000 total, 1000 started, 1000 done, 1000 succeeded, 0 failed, 0 errored
+    status codes: 1000 2xx, 0 3xx, 0 4xx, 0 5xx
+    traffic: 667500 bytes total, 28700 bytes headers, 612000 bytes data
+
+The number of ``failed`` is the number of requests returned with non
+2xx status.  The number of ``error`` is the number of ``failed`` plus
+the number of requests which failed with connection error.
+
+The number of ``total`` in ``traffic`` is the received application
+data.  If SSL/TLS is used, this number is calculated after decryption.
+The number of ``headers`` is the sum of payload size of response
+HEADERS (or SYN_REPLY for SPDY).  This number comes before
+decompressing header block.  The number of ``data`` is the sum of
+response body.
+
+Flow Control
+------------
+
+HTTP/2 and SPDY/3 or later employ flow control and it may affect
+benchmarking results.  To adjust receiver flow control window size,
+there is following options:
+
+``-w``
+   Sets  the stream  level  initial  window size  to
+   (2**<N>)-1.  For SPDY, 2**<N> is used instead.
+
+``-W``
+   Sets the connection level  initial window size to
+   (2**<N>)-1.  For  SPDY, if  <N> is  strictly less
+   than  16,  this  option  is  ignored.   Otherwise
+   2**<N> is used for SPDY.
+
+Multi-Threading
+---------------
+
+Sometimes benchmarking client itself becomes a bottleneck.  To remedy
+this situation, use ``-t`` option to specify the number of native
+thread to use.
+
+``-t``
+    The number of native threads. Default: 1
+
+Selecting protocol for clear text
+---------------------------------
+
+By default, if \http:// URI is given, HTTP/2 protocol is used.  To
+change the protocol to use for clear text, use ``-p`` option.
+
+Multiple URIs
+-------------
+
+If multiple URIs are specified, they are used in round robin manner.
+
+.. note::
+
+    Please note that h2load uses sheme, host and port in the first URI
+    and ignores those parts in the rest of the URIs.
diff --git a/doc/sources/index.rst b/doc/sources/index.rst
new file mode 100644 (file)
index 0000000..69b94d0
--- /dev/null
@@ -0,0 +1,50 @@
+.. nghttp2 documentation master file, created by
+   sphinx-quickstart on Sun Mar 11 22:57:49 2012.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+nghttp2 - HTTP/2 C Library
+============================
+
+This is an experimental implementation of Hypertext Transfer Protocol
+version 2.
+
+The project is hosted at `github.com/tatsuhiro-t/nghttp2 <https://github.com/tatsuhiro-t/nghttp2>`_.
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+   package_README
+   contribute
+   building-android-binary
+   tutorial-client
+   tutorial-server
+   tutorial-hpack
+   nghttp.1
+   nghttpd.1
+   nghttpx.1
+   h2load.1
+   nghttpx-howto
+   h2load-howto
+   apiref
+   libnghttp2_asio
+   python-apiref
+   nghttp2.h
+   nghttp2ver.h
+   asio_http2.h
+   Source <https://github.com/tatsuhiro-t/nghttp2>
+   Issues <https://github.com/tatsuhiro-t/nghttp2/issues>
+   nghttp2.org <https://nghttp2.org/>
+
+Released Versions
+=================
+
+https://github.com/tatsuhiro-t/nghttp2/releases
+
+Resources
+---------
+
+* http://tools.ietf.org/html/draft-ietf-httpbis-http2-14
+* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09
diff --git a/doc/sources/libnghttp2_asio.rst b/doc/sources/libnghttp2_asio.rst
new file mode 100644 (file)
index 0000000..bf544b1
--- /dev/null
@@ -0,0 +1,228 @@
+libnghttp2_asio: High level HTTP/2 C++ library
+==============================================
+
+libnghttp2_asio is C++ library built on top of libnghttp2 and provides
+high level abstraction API to build HTTP/2 applications.  It depends
+on Boost::ASIO library and OpenSSL.  Currently libnghttp2_asio
+provides server side API.
+
+libnghttp2_asio is not built by default.  Use ``--enable-asio-lib``
+configure flag to build libnghttp2_asio.  The required Boost libraries
+are:
+
+* Boost::Asio
+* Boost::System
+* Boost::Thread
+
+To use libnghttp2_asio, first include following header file:
+
+.. code-block:: cpp
+
+    #include <nghttp2/asio_http2.h>
+
+Also take a look at that header file :doc:`asio_http2.h`.
+
+Server API
+----------
+
+Server API is designed to build HTTP/2 server very easily to utilize
+C++11 anonymous function and closure.  The bare minimum example of
+HTTP/2 server looks like this:
+
+.. code-block:: cpp
+
+    #include <nghttp2/asio_http2.h>
+
+    using namespace nghttp2::asio_http2;
+    using namespace nghttp2::asio_http2::server;
+
+    int main(int argc, char *argv[]) {
+      http2 server;
+
+      server.listen("*", 3000, [](const std::shared_ptr<request> &req,
+                                  const std::shared_ptr<response> &res) {
+        res->write_head(200);
+        res->end("hello, world");
+      });
+    }
+
+First we instantiate ``nghttp2::asio_http2::server::http2`` object.
+Then call ``nghttp2::asio_http2::server::http2::listen`` function with
+address and port to listen to and callback function, namely "request
+callback", invoked when request arrives.
+
+The ``req`` and ``res`` represent HTTP request and response
+respectively.  ``nghttp2::asio_http2_::server::response::write_head``
+constructs HTTP response header fields.  The first argument is HTTP
+status code, in the above example, which is 200.  The second argument,
+which is omitted in the above example, is additional header fields to
+send.
+
+``nghttp2::asio_http2::server::response::end`` sends responde body.
+In the above example, we send string "hello, world".
+
+Serving static files and enabling SSL/TLS
++++++++++++++++++++++++++++++++++++++++++
+
+In this example, we serve a couple of static files and also enable
+SSL/TLS.
+
+.. code-block:: cpp
+
+    #include <nghttp2/asio_http2.h>
+
+    using namespace nghttp2::asio_http2;
+    using namespace nghttp2::asio_http2::server;
+
+    int main(int argc, char *argv[]) {
+      http2 server;
+
+      server.tls("server.key", "server.crt");
+
+      server.listen("*", 3000, [](const std::shared_ptr<request> &req,
+                                  const std::shared_ptr<response> &res) {
+        if (req->path() == "/" || req->path() == "/index.html") {
+          res->write_head(200);
+          res->end(file_reader("index.html"));
+        } else {
+          res->write_head(404);
+          res->end("<html><head><title>404</title></head>"
+                   "<body>404 Not Found</body></html>");
+        }
+      });
+    }
+
+Specifying path to private key file and certificate file in
+``nghttp2::asio_http2::server::http2::tls`` will enable SSL/TLS.  Both
+files must be in PEM format.
+
+In the above example, if request path is either "/" or "/index.html",
+we serve index.html file in the current working directory.
+``nghttp2::asio_http2::server::response::end`` has overload to take
+function of type ``nghttp2::asio_http2::read_cb`` and application pass
+its implementation to generate response body.  For the convenience,
+libnghttp2_asio library provides ``nghttp2::asio_http2::file_reader``
+function to generate function to server static file.
+
+Server push
++++++++++++
+
+Server push is also supported.
+
+.. code-block:: cpp
+
+    #include <nghttp2/asio_http2.h>
+
+    using namespace nghttp2::asio_http2;
+    using namespace nghttp2::asio_http2::server;
+
+    int main(int argc, char *argv[]) {
+      http2 server;
+
+      server.tls("server.key", "server.crt");
+
+      server.listen("*", 3000, [](const std::shared_ptr<request> &req,
+                                  const std::shared_ptr<response> &res) {
+        if (req->path() == "/") {
+          req->push("GET", "/my.css");
+
+          res->write_head(200);
+          res->end(file_reader("index.html"));
+
+          return;
+        }
+
+        if (req->path() == "/my.css") {
+          res->write_head(200);
+          res->end(file_reader("my.css"));
+
+          return;
+        }
+
+        res->write_head(404);
+        res->end("<html><head><title>404</title></head>"
+                 "<body>404 Not Found</body></html>");
+      });
+    }
+
+When client requested "/", we push "/my.css".  To push resource, call
+``nghttp2::asio_http2::server::request::push`` function with desired
+method and path.  Later, the callback will be called with the pushed
+resource "/my.css".
+
+Enable multi-threading
+++++++++++++++++++++++
+
+Enabling multi-threading is very easy.  Just call
+``nghttp2::asio_http2::server::http2::num_threads`` function with the
+desired number of threads:
+
+.. code-block:: cpp
+
+    http2 server;
+
+    // Use 4 native threads
+    server.num_threads(4);
+
+Run blocking tasks in background thread
++++++++++++++++++++++++++++++++++++++++
+
+The request callback is called in the same thread where HTTP request
+is handled.  And many connections shares the same thread, we cannot
+directly run blocking tasks in request callback.
+
+To run blocking tasks, use
+``nghttp2::asio_http2::server::request::run_task``.  The passed
+callback will be executed in the different thread from the thread
+where request callback was executed.  So application can perform
+blocking task there.  The example follows:
+
+.. code-block:: cpp
+
+    #include <unistd.h>
+    #include <nghttp2/asio_http2.h>
+
+    using namespace nghttp2::asio_http2;
+    using namespace nghttp2::asio_http2::server;
+
+    int main(int argc, char *argv[]) {
+      http2 server;
+
+      server.num_concurrent_tasks(16);
+
+      server.listen("*", 3000, [](const std::shared_ptr<request> &req,
+                                  const std::shared_ptr<response> &res) {
+        req->run_task([res](channel &channel) {
+          // executed in different thread than the thread where
+          // request callback was executed.
+
+          // using res directly here is not safe.  Capturing it by
+          // value is safe because it is std::shared_ptr.
+
+          sleep(1);
+
+          channel.post([res]() {
+            // executed in the same thread where request callback
+            // was executed.
+            res->write_head(200);
+            res->end("hello, world");
+          });
+        });
+      });
+    }
+
+First we set the number of background threads which run tasks.  By
+default it is set to 1.  In this example, we set it to 16, so at most
+16 tasks can be executed concurrently without blocking handling new
+requests.
+
+We call ``req->run_task()`` to execute task in background thread.  In
+the passed callback, we just simply sleeps 1 second.  After sleep is
+over, we schedule another callback to send response to the client.
+Since the callback passed to ``req->run_task()`` is executed in the
+different thread from the thread where request callback is called,
+using ``req`` or ``res`` object directly there may cause undefined
+behaviour.  To avoid this issue, we can use
+``nghttp2::asio_http2::channel::post`` by supplying a callback which
+in turn get called in the same thread where request callback was
+called.
diff --git a/doc/sources/nghttpx-howto.rst b/doc/sources/nghttpx-howto.rst
new file mode 100644 (file)
index 0000000..e81ea70
--- /dev/null
@@ -0,0 +1,295 @@
+nghttpx - HTTP/2 proxy - HOW-TO
+===============================
+
+nghttpx is a proxy translating protocols between HTTP/2 and other
+protocols (e.g., HTTP/1, SPDY).  It operates in several modes and each
+mode may require additional programs to work with.  This article
+describes each operation mode and explains the intended use-cases.  It
+also covers some useful options later.
+
+Default mode
+------------
+
+If nghttpx is invoked without any ``-s``, ``-p`` and ``--client``, it
+operates in default mode.  In this mode, nghttpx frontend listens for
+HTTP/2 requests and translates them to HTTP/1 requests.  Thus it works
+as reverse proxy (gateway) for HTTP/2 clients to HTTP/1 web server.
+HTTP/1 requests are also supported in frontend as a fallback.  If
+nghttpx is linked with spdylay library and frontend connection is
+SSL/TLS, the frontend also supports SPDY protocol.
+
+By default, this mode's frontend connection is encrypted using
+SSL/TLS.  So server's private key and certificate must be supplied to
+the command line (or through configuration file).  In this case, the
+fontend protocol selection will is done via ALPN or NPN.
+
+With ``--frontend-no-tls`` option, user can turn off SSL/TLS in
+frontend connection.  In this case, SPDY protocol is not available
+even if spdylay library is liked to nghttpx.  HTTP/2 and HTTP/1 are
+available on the frontend and a HTTP/1 connection can be upgraded to
+HTTP/2 using HTTP Upgrade.  Starting HTTP/2 connection by sending
+HTTP/2 connection preface is also supported.
+
+The backend is supposed to be HTTP/1 Web server.  For example, to make
+nghttpx listen to encrypted HTTP/2 requests at port 8443, and a
+backend HTTP/1 web server is configured to listen to HTTP/1 request at
+port 8080 in the same host, run nghttpx command-line like this::
+
+    $ nghttpx -f0.0.0.0,8443 -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
+
+Then HTTP/2 enabled client can access to the nghttpx in HTTP/2.  For
+example, you can send GET request to the server using nghttp::
+
+    $ nghttp -nv https://localhost:8443/
+
+HTTP/2 proxy mode
+-----------------
+
+If nghttpx is invoked with ``-s`` option, it operates in HTTP/2 proxy
+mode.  The supported protocols in frontend and backend connections are
+the same in `default mode`_.  The difference is that this mode acts
+like forward proxy and assumes the backend is HTTP/1 proxy server
+(e.g., squid, traffic server).  So HTTP/1 request must include
+absolute URI in request line.
+
+By default, frontend connection is encrypted.  So this mode is also
+called secure proxy.  If nghttpx is linked with spdylay, it supports
+SPDY protocols and it works as so called SPDY proxy.
+
+With ``--frontend-no-tls`` option, SSL/TLS is turned off in frontend
+connection, so the connection gets insecure.
+
+The backend must be HTTP/1 proxy server.  nghttpx only supports
+multiple backend server addresses.  It translates incoming requests to
+HTTP/1 request to backend server.  The backend server performs real
+proxy work for each request, for example, dispatching requests to the
+origin server and caching contents.
+
+For example, to make nghttpx listen to encrypted HTTP/2 requests at
+port 8443, and a backend HTTP/1 proxy server is configured to listen
+to HTTP/1 request at port 8080 in the same host, run nghttpx
+command-line like this::
+
+    $ nghttpx -s -f'*,8443' -b127.0.0.1,8080 /path/to/server.key /path/to/server.crt
+
+At the time of this writing, Firefox nightly supports HTTP/2 proxy.
+Chromium can use nghttpx as secure (SPDY) proxy and will support
+HTTP/2 proxy in the near future.
+
+To make Firefox nightly or Chromium use nghttpx as HTTP/2 or SPDY
+proxy, user has to create proxy.pac script file like this:
+
+.. code-block:: javascript
+
+    function FindProxyForURL(url, host) {
+        return "HTTPS SERVERADDR:PORT";
+    }
+
+``SERVERADDR`` and ``PORT`` is the hostname/address and port of the
+machine nghttpx is running.  Please note that both Firefox nightly and
+Chromium require valid certificate for secure proxy.
+
+For Firefox nightly, open Preference window and select Advanced then
+click Network tab.  Clicking Connection Settings button will show the
+dialog.  Select "Automatic proxy configuration URL" and enter the path
+to proxy.pac file, something like this::
+
+    file:///path/to/proxy.pac
+
+For Chromium, use following command-line::
+
+    $ google-chrome --proxy-pac-url=file:///path/to/proxy.pac --use-npn
+
+As HTTP/1 proxy server, Squid may work as out-of-box.  Traffic server
+requires to be configured as forward proxy.  Here is the minimum
+configuration items to edit::
+
+    CONFIG proxy.config.reverse_proxy.enabled INT 0
+    CONFIG proxy.config.url_remap.remap_required INT 0
+
+Consult Traffic server `documentation
+<https://docs.trafficserver.apache.org/en/latest/admin/forward-proxy.en.html>`_
+to know how to configure traffic server as forward proxy and its
+security implications.
+
+Client mode
+-----------
+
+If nghttpx is invoked with ``--client`` option, it operates in client
+mode.  In this mode, nghttpx listens for plain, unencrypted HTTP/2 and
+HTTP/1 requests and translates them to encrypted HTTP/2 requests to
+the backend.  User cannot enable SSL/TLS in frontend connection.
+
+HTTP/1 frontend connection can be upgraded to HTTP/2 using HTTP
+Upgrade.  To disable SSL/TLS in backend connection, use
+``--backend-no-tls`` option.
+
+The backend connection is created one per worker (thread).
+
+The backend server is supporsed to be a HTTP/2 web server (e.g.,
+nghttpd).  The one use-case of this mode is utilize existing HTTP/1
+clients to test HTTP/2 deployment.  Suppose that HTTP/2 web server
+listens to port 80 without encryption.  Then run nghttpx as client
+mode to access to that web server::
+
+    $ nghttpx --client -f127.0.0.1,8080 -b127.0.0.1,80 --backend-no-tls
+
+.. note::
+
+    You may need ``-k`` option if HTTP/2 server enables SSL/TLS and
+    its certificate is self-signed. But please note that it is
+    insecure.
+
+Then you can use curl to access HTTP/2 server via nghttpx::
+
+    $ curl http://localhost:8080/
+
+Client proxy mode
+-----------------
+
+If nghttpx is invoked with ``-p`` option, it operates in client proxy
+mode.  This mode behaves like `client mode`_, but it works like
+forward proxy.  So HTTP/1 request must include absolute URI in request
+line.
+
+HTTP/1 frontend connection can be upgraded to HTTP/2 using HTTP
+Upgrade.  To disable SSL/TLS in backend connection, use
+``--backend-no-tls`` option.
+
+The backend connection is created one per worker (thread).
+
+The backend server must be a HTTP/2 proxy.  You can use nghttpx in
+`HTTP/2 proxy mode`_ as backend server.  The one use-case of this mode
+is utilize existing HTTP/1 clients to test HTTP/2 connections between
+2 proxies. The another use-case is use this mode to aggregate local
+HTTP/1 connections to one HTTP/2 backend encrypted connection.  This
+makes HTTP/1 clients which does not support secure proxy can use
+secure HTTP/2 proxy via nghttpx client mode.
+
+Suppose that HTTP/2 proxy listens to port 8443, just like we saw in
+`HTTP/2 proxy mode`_.  To run nghttpx in client proxy mode to access
+that server, invoke nghttpx like this::
+
+    $ nghttpx -p -f127.0.0.1,8080 -b127.0.0.1,8443
+
+.. note::
+
+    You may need ``-k`` option if HTTP/2 server's certificate is
+    self-signed. But please note that it is insecure.
+
+Then you can use curl to issue HTTP request via HTTP/2 proxy::
+
+    $ curl --http-proxy=http://localhost:8080 http://www.google.com/
+
+You can configure web browser to use localhost:8080 as forward
+proxy.
+
+HTTP/2 bridge mode
+------------------
+
+If nghttpx is invoked with ``--http2-bridge`` option, it operates in
+HTTP/2 bridge mode.  The supported protocols in frontend connections
+are the same in `default mode`_.  The protocol in backend is HTTP/2
+only.
+
+With ``--frontend-no-tls`` option, SSL/TLS is turned off in frontend
+connection, so the connection gets insecure.  To disable SSL/TLS in
+backend connection, use ``--backend-no-tls`` option.
+
+The backend server is supporsed to be a HTTP/2 web server or HTTP/2
+proxy.  If backend server is HTTP/2 proxy, use
+``--no-location-rewrite`` and ``--no-host-rewrite`` options to disable
+rewriting location, host and :authority header field.
+
+The use-case of this mode is aggregate the incoming connections to one
+HTTP/2 connection.  One backend HTTP/2 connection is created per
+worker (thread).
+
+Disable SSL/TLS
+---------------
+
+In `default mode`_, `HTTP/2 proxy mode`_ and `HTTP/2 bridge mode`_,
+frontend connections are encrypted with SSL/TLS by default.  To turn
+off SSL/TLS, use ``--frontend-no-tls`` option.  If this option is
+used, the private key and certificate are not required to run nghttpx.
+
+In `client mode`_, `client proxy mode`_ and `HTTP/2 bridge mode`_,
+backend connections are encrypted with SSL/TLS by default.  To turn
+off SSL/TLS, use ``--backend-no-tls`` option.
+
+Specifying additional CA certificate
+------------------------------------
+
+By default, nghttpx tries to read CA certificate from system.  But
+depending on the system you use, this may fail or is not supported.
+To specify CA certificate manually, use ``--cacert`` option.  The
+specified file must be PEM format and can contain multiple
+certificates.
+
+By default, nghttpx validates server's certificate.  If you want to
+turn off this validation, knowing this is really insecure and what you
+are doing, you can use ``-k`` option to disable certificate
+validation.
+
+Read/write rate limit
+---------------------
+
+nghttpx supports transfer rate limiting on frontend connections.  You
+can do rate limit per frontend connection for reading and writeing
+individually.
+
+To perform rate limit for reading, use ``--read-rate`` and
+``--read-burst`` options.  For writing, use ``--write-rate`` and
+``--write-burst``.
+
+Please note that rate limit is performed on top of TCP and nothing to
+do with HTTP/2 flow control.
+
+Rewriting location header field
+-------------------------------
+
+nghttpx automatically rewrites location response header field if the
+following all conditions satisfy:
+
+* URI in location header field is not absolute URI or is not https URI.
+* URI in location header field includes non empty host component.
+* host (without port) in URI in location header field must match the
+  host appearing in :authority or host header field.
+
+When rewrite happens, URI scheme and port are replaced with the ones
+used in frontend, and host is replaced with which appears in
+:authority or host request header field.  :authority header field has
+precedence.  If the above conditions are not met with the host value
+in :authority header field, rewrite is retried with the value in host
+header field.
+
+Hot swapping
+------------
+
+nghttpx supports hot swapping using signals.  The hot swapping in
+nghttpx is multi step process.  First send USR2 signal to nghttpx
+process.  It will do fork and execute new executable, using same
+command-line arguments and environment variables.  At this point, both
+current and new processes can accept requests.  To gracefully shutdown
+current process, send QUIT signal to current nghttpx process.  When
+all existing frontend connections are done, the current process will
+exit.  At this point, only new nghttpx process exists and serves
+incoming requests.
+
+Re-opening log files
+--------------------
+
+When rotating log files, it is desirable to re-open log files after
+log rotation daemon renamed existing log files.  To tell nghttpx to
+re-open log files, send USR1 signal to nghttpx process.  It will
+re-open files specified by ``--accesslog-file`` and
+``--errorlog-file`` options.
+
+Multiple HTTP/1 backend addresses
+---------------------------------
+
+nghttpx supports multiple HTTP/1 backend addresses.  To specify them,
+just use ``-b`` option repeatedly.  For example, to use backend1:8080
+and backend2:8080, use command-line like this: ``-bbackend1,8080
+-bbackend2,8080``.  Please note that HTTP/2 backend only supports 1
+backend address.
diff --git a/doc/sources/python-apiref.rst b/doc/sources/python-apiref.rst
new file mode 100644 (file)
index 0000000..2996149
--- /dev/null
@@ -0,0 +1,319 @@
+Python API Reference
+====================
+
+.. py:module:: nghttp2
+
+nghttp2 offers some high level Python API to C library.  The bindings
+currently provide HPACK compressor and decompressor classes and HTTP/2
+server class.
+
+The extension module is called ``nghttp2``.
+
+``make`` will build the bindings.  The target Python version is
+determined by configure script.  If the detected Python version is not
+what you expect, specify a path to Python executable in ``PYTHON``
+variable as an argument to configure script (e.g., ``./configure
+PYTHON=/usr/bin/python3.4``).
+
+HPACK API
+---------
+
+.. py:class:: HDDeflater(hd_table_bufsize_max=DEFLATE_MAX_HEADER_TABLE_SIZE)
+
+   This class is used to perform header compression.  The
+   *hd_table_bufsize_max* limits the usage of header table in the
+   given amount of bytes.  The default value is
+   :py:data:`DEFLATE_MAX_HEADER_TABLE_SIZE`.  This is necessary
+   because the deflater and inflater share the same amount of header
+   table and the inflater decides that number.  The deflater may not
+   want to use all header table size because of limited memory
+   availability.  In that case, *hd_table_bufsize_max* can be used to
+   cap the upper limit of table size whatever the header table size is
+   chosen by the inflater.
+
+   .. py:method:: deflate(headers)
+
+      Deflates the *headers*. The *headers* must be sequence of tuple
+      of name/value pair, which are byte strings (not unicode string).
+
+      This method returns the deflated header block in byte string.
+      Raises the exception if any error occurs.
+
+   .. py:method:: set_no_refset(no_refset)
+
+      Tells the deflater not to use reference set if *no_refset* is
+      evaluated to ``True``.  If that happens, on each subsequent
+      invocation of :py:meth:`deflate()`, deflater will clear up
+      refersent set.
+
+   .. py:method:: change_table_size(hd_table_bufsize_max)
+
+      Changes header table size to *hd_table_bufsize_max* byte.  if
+      *hd_table_bufsize_max* is strictly larger than
+      ``hd_table_bufsize_max`` given in constructor,
+      ``hd_table_bufsize_max`` is used as header table size instead.
+
+      Raises the exception if any error occurs.
+
+   .. py:method:: get_hd_table()
+
+      Returns copy of current dynamic header table.
+
+The following example shows how to deflate header name/value pairs:
+
+.. code-block:: python
+
+   import binascii, nghttp2
+
+   deflater = nghttp2.HDDeflater()
+
+   res = deflater.deflate([(b'foo', b'bar'),
+                           (b'baz', b'buz')])
+
+   print(binascii.b2a_hex(res))
+
+
+.. py:class:: HDInflater()
+
+   This class is used to perform header decompression.
+
+   .. py:method:: inflate(data)
+
+      Inflates the deflated header block *data*. The *data* must be
+      byte string.
+
+      Raises the exception if any error occurs.
+
+   .. py:method:: change_table_size(hd_table_bufsize_max)
+
+      Changes header table size to *hd_table_bufsize_max* byte.
+
+      Raises the exception if any error occurs.
+
+   .. py:method:: get_hd_table()
+
+      Returns copy of current dynamic header table.
+
+The following example shows how to inflate deflated header block:
+
+.. code-block:: python
+
+   deflater = nghttp2.HDDeflater()
+
+   data = deflater.deflate([(b'foo', b'bar'),
+                            (b'baz', b'buz')])
+
+   inflater = nghttp2.HDInflater()
+
+   hdrs = inflater.inflate(data)
+
+   print(hdrs)
+
+
+.. py:function:: print_hd_table(hdtable)
+
+   Convenient function to print *hdtable* to the standard output.  The
+   *hdtable* is the one retrieved by
+   :py:meth:`HDDeflater.get_hd_table()` or
+   :py:meth:`HDInflater.get_hd_table()`.  This function does not work
+   if header name/value cannot be decoded using UTF-8 encoding.
+
+   In output, ``s=N`` means the entry occupies ``N`` bytes in header
+   table.  If ``r=y``, then the entry is in the reference set.
+
+.. py:data:: DEFAULT_HEADER_TABLE_SIZE
+
+   The default header table size, which is 4096 as per HTTP/2
+   specification.
+
+.. py:data:: DEFLATE_MAX_HEADER_TABLE_SIZE
+
+   The default header table size for deflater.  The initial value
+   is 4096.
+
+HTTP/2 servers
+--------------
+
+.. note::
+
+   We use :py:mod:`asyncio` for HTTP/2 server classes.  Therefore,
+   Python 3.4 or later is required to use these objects.  To
+   explicitly configure nghttp2 build to use Python 3.4, specify the
+   ``PYTHON`` variable to the path to Python 3.4 executable when
+   invoking configure script like this::
+
+       $ ./configure PYTHON=/usr/bin/python3.4
+
+.. py:class:: HTTP2Server(address, RequestHandlerClass, ssl=None)
+
+   This class builds on top of the :py:mod:`asyncio` event loop.  On
+   construction, *RequestHandlerClass* must be given, which must be a
+   subclass of :py:class:`BaseRequestHandler` class.
+
+   The *address* must be a tuple of hostname/IP address and port to
+   bind.  If hostname/IP address is ``None``, all interfaces are
+   assumed.
+
+   To enable SSL/TLS, specify instance of :py:class:`ssl.SSLContext`
+   in *ssl*.  Before passing *ssl* to
+   :py:func:`BaseEventLoop.create_server`, ALPN protocol identifiers
+   are set using :py:meth:`ssl.SSLContext.set_npn_protocols`.
+
+   To disable SSL/TLS, omit *ssl* or specify ``None``.
+
+   .. py:method:: serve_forever()
+
+      Runs server and processes incoming requests forever.
+
+.. py:class:: BaseRequestHandler(http2, stream_id)
+
+   The class is used to handle the single HTTP/2 stream.  By default,
+   it does not nothing.  It must be subclassed to handle each event
+   callback method.
+
+   The first callback method invoked is :py:meth:`on_headers()`. It is
+   called when HEADERS frame, which includes request header fields, is
+   arrived.
+
+   If request has request body, :py:meth:`on_data()` is invoked for
+   each chunk of received data chunk.
+
+   When whole request is received, :py:meth:`on_request_done()` is
+   invoked.
+
+   When stream is closed, :py:meth:`on_close()` is called.
+
+   The application can send response using :py:meth:`send_response()`
+   method.  It can be used in :py:meth:`on_headers()`,
+   :py:meth:`on_data()` or :py:meth:`on_request_done()`.
+
+   The application can push resource using :py:meth:`push()` method.
+   It must be used before :py:meth:`send_response()` call.
+
+   A :py:class:`BaseRequestHandler` has the following instance
+   variables:
+
+   .. py:attribute:: client_address
+
+      Contains a tuple of the form ``(host, port)`` referring to the
+      client's address.
+
+   .. py:attribute:: stream_id
+
+      Stream ID of this stream
+
+   .. py:attribute:: scheme
+
+      Scheme of the request URI.  This is a value of ``:scheme``
+      header field.
+
+   .. py:attribute:: method
+
+      Method of this stream.  This is a value of ``:method`` header
+      field.
+
+   .. py:attribute:: host
+
+      This is a value of ``:authority`` or ``host`` header field.
+
+   .. py:attribute:: path
+
+      This is a value of ``:path`` header field.
+
+   A :py:class:`BaseRequestHandler` has the following methods:
+
+   .. py:method:: on_headers()
+
+      Called when request HEADERS is arrived.  By default, this method
+      does nothing.
+
+   .. py:method:: on_data(data)
+
+      Called when a chunk of request body *data* is arrived.  This
+      method will be called multiple times until all data are
+      received.  By default, this method does nothing.
+
+   .. py:method:: on_request_done()
+
+      Called when whole request was received.  By default, this method
+      does nothing.
+
+   .. py:method:: on_close(error_code)
+
+      Called when stream is about to close.  The *error_code*
+      indicates the reason of closure.  If it is ``0``, the stream is
+      going to close without error.
+
+   .. py:method:: send_response(status=200, headers=None, body=None)
+
+      Send response.  The *status* is HTTP status code.  The *headers*
+      is additional response headers.  The *:status* header field will
+      be appended by the library.  The *body* is the response body.
+      It could be ``None`` if response body is empty. Or it must be
+      instance of either ``str``, ``bytes`` or :py:class:`io.IOBase`.
+      If instance of ``str`` is specified, it will be encoded using
+      UTF-8.
+
+      The *headers* is a list of tuple of the form ``(name,
+      value)``. The ``name`` and ``value`` can be either byte string
+      or unicode string.  In the latter case, they will be encoded
+      using UTF-8.
+
+      Raises the exception if any error occurs.
+
+   .. py:method:: push(path, method='GET', request_headers=None, status=200, headers=None, body=None)
+
+      Push a specified resource.  The *path* is a path portion of
+      request URI for this resource.  The *method* is a method to
+      access this resource.  The *request_headers* is additional
+      request headers to access this resource.  The ``:scheme``,
+      ``:method``, ``:authority`` and ``:path`` are appended by the
+      library.  The ``:scheme`` and ``:authority`` are inherited from
+      request header fields of the associated stream.
+
+      The *status* is HTTP status code.  The *headers* is additional
+      response headers.  The ``:status`` header field is appended by
+      the library.  The *body* is the response body.  It could be
+      ``None`` if response body is empty.  Or it must be instance of
+      either ``str``, ``bytes`` or ``io.IOBase``.  If instance of
+      ``str`` is specified, it is encoded using UTF-8.
+
+      The headers and request_headers are a list of tuple of the form
+      ``(name, value)``. The ``name`` and ``value`` can be either byte
+      string or unicode string.  In the latter case, they will be
+      encoded using UTF-8.
+
+      Returns an instance of ``RequestHandlerClass`` specified in
+      :py:class:`HTTP2Server` constructor for the pushed resource.
+
+      Raises the exception if any error occurs.
+
+The following example illustrates :py:class:`HTTP2Server` and
+:py:class:`BaseRequestHandler` usage:
+
+.. code-block:: python
+
+    #!/usr/bin/env python
+
+    import io, ssl
+    import nghttp2
+
+    class Handler(nghttp2.BaseRequestHandler):
+
+        def on_headers(self):
+            self.push(path='/css/style.css',
+                      request_headers = [('content-type', 'text/css')],
+                      status=200,
+                      body='body{margin:0;}')
+
+            self.send_response(status=200,
+                               headers = [('content-type', 'text/plain')],
+                               body=io.BytesIO(b'nghttp2-python FTW'))
+
+    ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+    ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2
+    ctx.load_cert_chain('server.crt', 'server.key')
+
+    # give None to ssl to make the server non-SSL/TLS
+    server = nghttp2.HTTP2Server(('127.0.0.1', 8443), Handler, ssl=ctx)
+    server.serve_forever()
diff --git a/doc/sources/tutorial-client.rst b/doc/sources/tutorial-client.rst
new file mode 100644 (file)
index 0000000..0d16899
--- /dev/null
@@ -0,0 +1,440 @@
+Tutorial: HTTP/2 client
+=========================
+
+In this tutorial, we are going to write very primitive HTTP/2
+client. The complete source code, `libevent-client.c`_, is attached at
+the end of this page.  It also resides in examples directory in the
+archive or repository.
+
+This simple client takes 1 argument, HTTPS URI, and retrieves the
+resource denoted by the URI. Its synopsis is like this::
+
+    $ libevent-client HTTPS_URI
+
+We use libevent in this tutorial to handle networking I/O.  Please
+note that nghttp2 itself does not depend on libevent.
+
+First we do some setup routine for libevent and OpenSSL library in
+function ``main()`` and ``run()``, which is not so relevant to nghttp2
+library use. The one thing you should look at is setup NPN callback.
+The NPN callback is used for the client to select the next application
+protocol over the SSL/TLS transport. In this tutorial, we use
+`nghttp2_select_next_protocol()` function to select the HTTP/2
+protocol the library supports::
+
+    static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
+                                    unsigned char *outlen, const unsigned char *in,
+                                    unsigned int inlen, void *arg _U_) {
+      if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
+        errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
+      }
+      return SSL_TLSEXT_ERR_OK;
+    }
+
+The callback is set to the SSL_CTX object using
+``SSL_CTX_set_next_proto_select_cb()`` function::
+
+    static SSL_CTX *create_ssl_ctx(void) {
+      SSL_CTX *ssl_ctx;
+      ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+      if (!ssl_ctx) {
+        errx(1, "Could not create SSL/TLS context: %s",
+             ERR_error_string(ERR_get_error(), NULL));
+      }
+      SSL_CTX_set_options(ssl_ctx,
+                          SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                              SSL_OP_NO_COMPRESSION |
+                              SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+      SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
+      return ssl_ctx;
+    }
+
+We use ``http2_session_data`` structure to store the data related to
+the HTTP/2 session::
+
+    typedef struct {
+      nghttp2_session *session;
+      struct evdns_base *dnsbase;
+      struct bufferevent *bev;
+      http2_stream_data *stream_data;
+    } http2_session_data;
+
+Since this program only handles 1 URI, it uses only 1 stream. We store
+its stream specific data in ``http2_stream_data`` structure and the
+``stream_data`` points to it. The ``struct http2_stream_data`` is
+defined as follows::
+
+    typedef struct {
+      /* The NULL-terminated URI string to retreive. */
+      const char *uri;
+      /* Parsed result of the |uri| */
+      struct http_parser_url *u;
+      /* The authroity portion of the |uri|, not NULL-terminated */
+      char *authority;
+      /* The path portion of the |uri|, including query, not
+         NULL-terminated */
+      char *path;
+      /* The length of the |authority| */
+      size_t authoritylen;
+      /* The length of the |path| */
+      size_t pathlen;
+      /* The stream ID of this stream */
+      int32_t stream_id;
+    } http2_stream_data;
+
+We creates and initializes these structures in
+``create_http2_session_data()`` and ``create_http2_stream_data()``
+respectively.
+
+Then we call function ``initiate_connection()`` to start connecting to
+the remote server::
+
+    static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
+                                    const char *host, uint16_t port,
+                                    http2_session_data *session_data) {
+      int rv;
+      struct bufferevent *bev;
+      SSL *ssl;
+
+      ssl = create_ssl(ssl_ctx);
+      bev = bufferevent_openssl_socket_new(
+          evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
+          BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
+      bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
+      rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
+                                               AF_UNSPEC, host, port);
+
+      if (rv != 0) {
+        errx(1, "Could not connect to the remote host %s", host);
+      }
+      session_data->bev = bev;
+    }
+
+We set 3 callbacks for the bufferevent: ``reacb``, ``writecb`` and
+``eventcb``.
+
+The ``eventcb()`` is invoked by libevent event loop when an event
+(e.g., connection has been established, timeout, etc) happens on the
+underlying network socket::
+
+    static void eventcb(struct bufferevent *bev, short events, void *ptr) {
+      http2_session_data *session_data = (http2_session_data *)ptr;
+      if (events & BEV_EVENT_CONNECTED) {
+        int fd = bufferevent_getfd(bev);
+        int val = 1;
+        fprintf(stderr, "Connected\n");
+        setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+        initialize_nghttp2_session(session_data);
+        send_client_connection_header(session_data);
+        submit_request(session_data);
+        if (session_send(session_data) != 0) {
+          delete_http2_session_data(session_data);
+        }
+        return;
+      }
+      if (events & BEV_EVENT_EOF) {
+        warnx("Disconnected from the remote host");
+      } else if (events & BEV_EVENT_ERROR) {
+        warnx("Network error");
+      } else if (events & BEV_EVENT_TIMEOUT) {
+        warnx("Timeout");
+      }
+      delete_http2_session_data(session_data);
+    }
+
+For ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR`` and ``BEV_EVENT_TIMEOUT``
+event, we just simply tear down the connection. The
+``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake is
+finished successfully. We first initialize nghttp2 session object in
+``initialize_nghttp2_session()`` function::
+
+    static void initialize_nghttp2_session(http2_session_data *session_data) {
+      nghttp2_session_callbacks *callbacks;
+
+      nghttp2_session_callbacks_new(&callbacks);
+
+      nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+      nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                           on_frame_recv_callback);
+
+      nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+          callbacks, on_data_chunk_recv_callback);
+
+      nghttp2_session_callbacks_set_on_stream_close_callback(
+          callbacks, on_stream_close_callback);
+
+      nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                       on_header_callback);
+
+      nghttp2_session_callbacks_set_on_begin_headers_callback(
+          callbacks, on_begin_headers_callback);
+
+      nghttp2_session_client_new(&session_data->session, callbacks, session_data);
+
+      nghttp2_session_callbacks_del(callbacks);
+    }
+
+Since we are creating client, we use `nghttp2_session_client_new()` to
+initialize nghttp2 session object.  We setup 7 callbacks for the
+nghttp2 session. We'll explain these callbacks later.
+
+The `delete_http2_session_data()` destroys ``session_data`` and frees
+its bufferevent, so it closes underlying connection as well. It also
+calls `nghttp2_session_del()` to delete nghttp2 session object.
+
+We begin HTTP/2 communication by sending client connection preface,
+which is 24 bytes magic byte sequence
+(:macro:`NGHTTP2_CLIENT_CONNECTION_PREFACE`) and SETTINGS frame.  The
+transmission of client connection header is done in
+``send_client_connection_header()``::
+
+    static void send_client_connection_header(http2_session_data *session_data) {
+      nghttp2_settings_entry iv[1] = {
+          {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+      int rv;
+
+      bufferevent_write(session_data->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
+                        NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+      rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
+                                   ARRLEN(iv));
+      if (rv != 0) {
+        errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
+      }
+    }
+
+Here we specify SETTINGS_MAX_CONCURRENT_STREAMS to 100, which is
+really not needed for this tiny example progoram, but we are
+demonstrating the use of SETTINGS frame. To queue the SETTINGS frame
+for the transmission, we use `nghttp2_submit_settings()`. Note that
+`nghttp2_submit_settings()` function only queues the frame and not
+actually send it. All ``nghttp2_submit_*()`` family functions have
+this property. To actually send the frame, `nghttp2_session_send()` is
+used, which is described about later.
+
+After the transmission of client connection header, we enqueue HTTP
+request in ``submit_request()`` function::
+
+    static void submit_request(http2_session_data *session_data) {
+      int32_t stream_id;
+      http2_stream_data *stream_data = session_data->stream_data;
+      const char *uri = stream_data->uri;
+      const struct http_parser_url *u = stream_data->u;
+      nghttp2_nv hdrs[] = {
+          MAKE_NV2(":method", "GET"),
+          MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
+                  u->field_data[UF_SCHEMA].len),
+          MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
+          MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
+      fprintf(stderr, "Request headers:\n");
+      print_headers(stderr, hdrs, ARRLEN(hdrs));
+      stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
+                                         ARRLEN(hdrs), NULL, stream_data);
+      if (stream_id < 0) {
+        errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
+      }
+
+      stream_data->stream_id = stream_id;
+    }
+
+We build HTTP request header fields in ``hdrs`` which is an array of
+:type:`nghttp2_nv`. There are 4 header fields to be sent: ``:method``,
+``:scheme``, ``:authority`` and ``:path``. To queue this HTTP request,
+we use `nghttp2_submit_request()` function. The `stream_data` is
+passed in *stream_user_data* parameter. It is used in nghttp2
+callbacks which we'll describe about later.
+`nghttp2_submit_request()` returns the newly assigned stream ID for
+this request.
+
+The next bufferevent callback is ``readcb()``, which is invoked when
+data is available to read in the bufferevent input buffer::
+
+    static void readcb(struct bufferevent *bev, void *ptr) {
+      http2_session_data *session_data = (http2_session_data *)ptr;
+      ssize_t readlen;
+      struct evbuffer *input = bufferevent_get_input(bev);
+      size_t datalen = evbuffer_get_length(input);
+      unsigned char *data = evbuffer_pullup(input, -1);
+
+      readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
+      if (readlen < 0) {
+        warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
+        delete_http2_session_data(session_data);
+        return;
+      }
+      if (evbuffer_drain(input, readlen) != 0) {
+        warnx("Fatal error: evbuffer_drain failed");
+        delete_http2_session_data(session_data);
+        return;
+      }
+      if (session_send(session_data) != 0) {
+        delete_http2_session_data(session_data);
+        return;
+      }
+    }
+
+In this function, we feed all unprocessed, received data to nghttp2
+session object using `nghttp2_session_mem_recv()` function. The
+`nghttp2_session_mem_recv()` processes the received data and may
+invoke nghttp2 callbacks and also queue frames. Since there may be
+pending frames, we call ``session_send()`` function to send those
+frames. The ``session_send()`` function is defined as follows::
+
+    static int session_send(http2_session_data *session_data) {
+      int rv;
+
+      rv = nghttp2_session_send(session_data->session);
+      if (rv != 0) {
+        warnx("Fatal error: %s", nghttp2_strerror(rv));
+        return -1;
+      }
+      return 0;
+    }
+
+The `nghttp2_session_send()` function serializes the frame into wire
+format and call ``send_callback()`` function of type
+:type:`nghttp2_send_callback`.  The ``send_callback()`` is defined as
+follows::
+
+    static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
+                                 size_t length, int flags _U_, void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      struct bufferevent *bev = session_data->bev;
+      bufferevent_write(bev, data, length);
+      return length;
+    }
+
+Since we use bufferevent to abstract network I/O, we just write the
+data to the bufferevent object. Note that `nghttp2_session_send()`
+continues to write all frames queued so far. If we were writing the
+data to the non-blocking socket directly using ``write()`` system call
+in the ``send_callback()``, we will surely get ``EAGAIN`` or
+``EWOULDBLOCK`` since the socket has limited send buffer. If that
+happens, we can return :macro:`NGHTTP2_ERR_WOULDBLOCK` to signal the
+nghttp2 library to stop sending further data. But writing to the
+bufferevent, we have to regulate the amount data to be buffered by
+ourselves to avoid possible huge memory consumption. In this example
+client, we do not limit anything. To see how to regulate the amount of
+buffered data, see the ``send_callback()`` in the server tutorial.
+
+The third bufferevent callback is ``writecb()``, which is invoked when
+all data written in the bufferevent output buffer have been sent::
+
+    static void writecb(struct bufferevent *bev _U_, void *ptr) {
+      http2_session_data *session_data = (http2_session_data *)ptr;
+      if (nghttp2_session_want_read(session_data->session) == 0 &&
+          nghttp2_session_want_write(session_data->session) == 0 &&
+          evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
+        delete_http2_session_data(session_data);
+      }
+    }
+
+As described earlier, we just write off all data in `send_callback()`,
+we have no data to write in this function. All we have to do is check
+we have to drop connection or not. The nghttp2 session object keeps
+track of reception and transmission of GOAWAY frame and other error
+conditions as well. Using these information, nghttp2 session object
+will tell whether the connection should be dropped or not. More
+specifically, both `nghttp2_session_want_read()` and
+`nghttp2_session_want_write()` return 0, we have no business in the
+connection. But since we are using bufferevent and its deferred
+callback option, the bufferevent output buffer may contain the pending
+data when the ``writecb()`` is called. To handle this situation, we
+also check whether the output buffer is empty or not. If these
+conditions are met, we drop connection.
+
+We have already described about nghttp2 callback ``send_callback()``.
+Let's describe remaining nghttp2 callbacks we setup in
+``initialize_nghttp2_setup()`` function.
+
+Each request header name/value pair is emitted via
+``on_header_callback`` function::
+
+    static int on_header_callback(nghttp2_session *session _U_,
+                                  const nghttp2_frame *frame, const uint8_t *name,
+                                  size_t namelen, const uint8_t *value,
+                                  size_t valuelen, uint8_t flags _U_,
+                                  void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      switch (frame->hd.type) {
+      case NGHTTP2_HEADERS:
+        if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+            session_data->stream_data->stream_id == frame->hd.stream_id) {
+          /* Print response headers for the initiated request. */
+          print_header(stderr, name, namelen, value, valuelen);
+          break;
+        }
+      }
+      return 0;
+    }
+
+In this tutorial, we just print the name/value pair.
+
+After all name/value pairs are emitted for a frame,
+``on_frame_recv_callback`` function is called::
+
+    static int on_frame_recv_callback(nghttp2_session *session _U_,
+                                      const nghttp2_frame *frame, void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      switch (frame->hd.type) {
+      case NGHTTP2_HEADERS:
+        if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+            session_data->stream_data->stream_id == frame->hd.stream_id) {
+          fprintf(stderr, "All headers received\n");
+        }
+        break;
+      }
+      return 0;
+    }
+
+In this tutorial, we are just interested in the HTTP response
+HEADERS. We check te frame type and its category (it should be
+:macro:`NGHTTP2_HCAT_RESPONSE` for HTTP response HEADERS). Also check
+its stream ID.
+
+The ``on_data_chunk_recv_callback()`` function is invoked when a chunk
+of data is received from the remote peer::
+
+    static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
+                                           uint8_t flags _U_, int32_t stream_id,
+                                           const uint8_t *data, size_t len,
+                                           void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      if (session_data->stream_data->stream_id == stream_id) {
+        fwrite(data, len, 1, stdout);
+      }
+      return 0;
+    }
+
+In our case, a chunk of data is response body. After checking stream
+ID, we just write the recieved data to the stdout. Note that the
+output in the terminal may be corrupted if the response body contains
+some binary data.
+
+The ``on_stream_close_callback()`` function is invoked when the stream
+is about to close::
+
+    static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                                        nghttp2_error_code error_code,
+                                        void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      int rv;
+
+      if (session_data->stream_data->stream_id == stream_id) {
+        fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
+                error_code);
+        rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+        if (rv != 0) {
+          return NGHTTP2_ERR_CALLBACK_FAILURE;
+        }
+      }
+      return 0;
+    }
+
+If the stream ID matches the one we initiated, it means that its
+stream is going to be closed. Since we have finished to get the
+resource we want (or the stream was reset by RST_STREAM from the
+remote peer), we call `nghttp2_session_terminate_session()` to
+commencing the closure of the HTTP/2 session gracefully. If you have
+some data associated for the stream to be closed, you may delete it
+here.
diff --git a/doc/sources/tutorial-hpack.rst b/doc/sources/tutorial-hpack.rst
new file mode 100644 (file)
index 0000000..3bfc834
--- /dev/null
@@ -0,0 +1,118 @@
+Tutorial: HPACK API
+===================
+
+In this tutorial, we describe basic use of HPACK API in nghttp2
+library.  We briefly describe APIs for deflating and inflating header
+fields.  The example of using these APIs are presented as complete
+source code `deflate.c`_.
+
+Deflating (encoding) headers
+----------------------------
+
+First we need to initialize :type:`nghttp2_hd_deflater` object using
+`nghttp2_hd_deflate_new()` function::
+
+    int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
+                               size_t deflate_hd_table_bufsize_max);
+
+This function allocates :type:`nghttp2_hd_deflater` object and
+initializes it and assigns its pointer to ``*deflater_ptr`` passed by
+parameter.  The *deflate_hd_table_bufsize_max* is the upper bound of
+header table size the deflater will use.  This will limit the memory
+usage in deflater object for dynamic header table.  If you doubt, just
+specify 4096 here, which is the default upper bound of dynamic header
+table buffer size.
+
+To encode header fields, `nghttp2_hd_deflate_hd()` function::
+
+    ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater,
+                                  uint8_t *buf, size_t buflen,
+                                  const nghttp2_nv *nva, size_t nvlen);
+
+The *deflater* is the deflater object initialized by
+`nghttp2_hd_deflate_new()` function described above.  The *buf* is a
+pointer to buffer to store encoded byte string.  The *buflen* is
+capacity of *buf*.  The *nva* is a pointer to :type:`nghttp2_nv`,
+which is an array of header fields to deflate.  The *nvlen* is the
+number of header fields which *nva* contains.
+
+It is important to initialize and assign all members of
+:type:`nghttp2_nv`.  If a header field should not be inserted in
+dynamic header table for a security reason, set
+:macro:`NGHTTP2_NV_FLAG_NO_INDEX` flag in :member:`nghttp2_nv.flags`.
+
+`nghttp2_hd_deflate_hd()` processes all headers given in *nva*.  The
+*nva* must include all request or response header fields to be sent in
+one HEADERS (or optionally following (multiple) CONTINUATION
+frame(s)).  The *buf* must have enough space to store the encoded
+result.  Otherwise, the function will fail.  To estimate the upper
+bound of encoded result, use `nghttp2_hd_deflate_bound()` function::
+
+    size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
+                                    const nghttp2_nv *nva, size_t nvlen);
+
+Pass this function with the same paramters *deflater*, *nva* and
+*nvlen* which will be passed to `nghttp2_hd_deflate_hd()`.
+
+The subsequent call of `nghttp2_hd_deflate_hd()` will use current
+encoder state and perform differential encoding which is the
+fundamental compression gain for HPACK.
+
+Once `nghttp2_hd_deflate_hd()` fails, it cannot be undone and its
+further call with the same deflater object shall fail.  So it is very
+important to use `nghttp2_hd_deflate_bound()` to know the required
+size of buffer.
+
+To delete :type:`nghttp2_hd_deflater` object, use `nghttp2_hd_deflate_del()`
+function.
+
+Inflating (decoding) headers
+----------------------------
+
+We use :type:`nghttp2_hd_inflater` object to inflate compressed header
+data.  To initialize the object, use `nghttp2_hd_inflate_new()`::
+
+    int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr);
+
+To inflate header data, use `nghttp2_hd_inflate_hd()` function::
+
+    ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
+                                  nghttp2_nv *nv_out, int *inflate_flags,
+                                  uint8_t *in, size_t inlen, int in_final);
+
+The *inflater* is the inflater object initialized above.  The *nv_out*
+is a pointer to :type:`nghttp2_nv` to store the result.  The *in* is a
+pointer to input data and *inlen* is its length.  The caller is not
+required to specify whole deflated header data to *in* at once.  It
+can call this function multiple times for portion of the data in
+streaming way.  If *in_final* is nonzero, it tells the function that
+the passed data is the final sequence of deflated header data.  The
+*inflate_flags* is output parameter and successful call of this
+function stores a set of flags in it.  It will be described later.
+
+This function returns when each header field is inflated.  When this
+happens, the function sets :macro:`NGHTTP2_HD_INFLATE_EMIT` flag to
+*inflate_flag* parameter and header field is stored in *nv_out*.  The
+return value indicates the number of data read from *in* to processed
+so far.  It may be less than *inlen*.  The caller should call the
+function repeatedly until all data are processed by adjusting *in* and
+*inlen* with the processed bytes.
+
+If *in_final* is nonzero and all given data was processed, the
+function sets :macro:`NGHTTP2_HD_INFLATE_FINAL` flag to
+*inflate_flag*.  If the caller sees this flag set, call
+`nghttp2_hd_inflate_end_headers()` function.
+
+If *in_final* is zero and :macro:`NGHTTP2_HD_INFLATE_EMIT` flag is not
+set, it indicates that all given data was processed.  The caller is
+required to pass subsequent data.
+
+It is important to note that the function may produce one or more
+header fields even if *inlen* is 0 when *in_final* is nonzero, due to
+differential encoding.
+
+The example use of `nghttp2_hd_inflate_hd()` is shown in
+`inflate_header_block()` function in `deflate.c`_.
+
+To delete :type:`nghttp2_hd_inflater` object, use `nghttp2_hd_inflate_del()`
+function.
diff --git a/doc/sources/tutorial-server.rst b/doc/sources/tutorial-server.rst
new file mode 100644 (file)
index 0000000..12347ef
--- /dev/null
@@ -0,0 +1,562 @@
+Tutorial: HTTP/2 server
+=========================
+
+In this tutorial, we are going to write single-threaded, event-based
+HTTP/2 web server, which supports HTTPS only. It can handle
+concurrent multiple requests, but only the GET method is supported. The
+complete source code, `libevent-server.c`_, is attached at the end of
+this page.  It also resides in examples directory in the archive or
+repository.
+
+This simple server takes 3 arguments, a port number to listen to, a path to
+your SSL/TLS private key file and a path to your certificate file.  Its
+synopsis is like this::
+
+    $ libevent-server PORT /path/to/server.key /path/to/server.crt
+
+We use libevent in this tutorial to handle networking I/O.  Please
+note that nghttp2 itself does not depend on libevent.
+
+First we create a setup routine for libevent and OpenSSL in the functions
+``main()`` and ``run()``. One thing in there you should look at, is the setup
+of the NPN callback.  The NPN callback is used for the server to advertise
+which application protocols the server supports to a client.  In this example
+program, when creating ``SSL_CTX`` object, we store the application protocol
+name in the wire format of NPN in a statically allocated buffer. This is safe
+because we only create one ``SSL_CTX`` object in the program's entire life
+time::
+
+    static unsigned char next_proto_list[256];
+    static size_t next_proto_list_len;
+
+    static int next_proto_cb(SSL *s _U_, const unsigned char **data,
+                             unsigned int *len, void *arg _U_) {
+      *data = next_proto_list;
+      *len = (unsigned int)next_proto_list_len;
+      return SSL_TLSEXT_ERR_OK;
+    }
+
+    static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
+      SSL_CTX *ssl_ctx;
+      EC_KEY *ecdh;
+
+      ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+
+      ...
+
+      next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN;
+      memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID,
+             NGHTTP2_PROTO_VERSION_ID_LEN);
+      next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN;
+
+      SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, NULL);
+      return ssl_ctx;
+    }
+
+The wire format of NPN is a sequence of length prefixed string. Exactly one
+byte is used to specify the length of each protocol identifier.  In this
+tutorial, we advertise the specific HTTP/2 protocol version the current
+nghttp2 library supports. The nghttp2 library exports its identifier in
+:macro:`NGHTTP2_PROTO_VERSION_ID`. The ``next_proto_cb()`` function is the
+server-side NPN callback. In the OpenSSL implementation, we just assign the
+pointer to the NPN buffers we filled in earlier. The NPN callback function is
+set to the ``SSL_CTX`` object using
+``SSL_CTX_set_next_protos_advertised_cb()``.
+
+We use the ``app_content`` structure to store application-wide data::
+
+    struct app_context {
+      SSL_CTX *ssl_ctx;
+      struct event_base *evbase;
+    };
+
+We use the ``http2_session_data`` structure to store session-level
+(which corresponds to one HTTP/2 connection) data::
+
+    typedef struct http2_session_data {
+      struct http2_stream_data root;
+      struct bufferevent *bev;
+      app_context *app_ctx;
+      nghttp2_session *session;
+      char *client_addr;
+    } http2_session_data;
+
+We use the ``http2_stream_data`` structure to store stream-level data::
+
+    typedef struct http2_stream_data {
+      struct http2_stream_data *prev, *next;
+      char *request_path;
+      int32_t stream_id;
+      int fd;
+    } http2_stream_data;
+
+A single HTTP/2 session can have multiple streams.  We manage these
+multiple streams with a doubly linked list.  The first element of this
+list is pointed to by the ``root->next`` in ``http2_session_data``.
+Initially, ``root->next`` is ``NULL``.  We use libevent's bufferevent
+structure to perform network I/O.  Note that the bufferevent object is
+kept in ``http2_session_data`` and not in ``http2_stream_data``.  This
+is because ``http2_stream_data`` is just a logical stream multiplexed
+over the single connection managed by bufferevent in
+``http2_session_data``.
+
+We first create a listener object to accept incoming connections.  We use
+libevent's ``struct evconnlistener`` for this purpose::
+
+    static void start_listen(struct event_base *evbase, const char *service,
+                             app_context *app_ctx) {
+      int rv;
+      struct addrinfo hints;
+      struct addrinfo *res, *rp;
+
+      memset(&hints, 0, sizeof(hints));
+      hints.ai_family = AF_UNSPEC;
+      hints.ai_socktype = SOCK_STREAM;
+      hints.ai_flags = AI_PASSIVE;
+    #ifdef AI_ADDRCONFIG
+      hints.ai_flags |= AI_ADDRCONFIG;
+    #endif /* AI_ADDRCONFIG */
+
+      rv = getaddrinfo(NULL, service, &hints, &res);
+      if (rv != 0) {
+        errx(1, NULL);
+      }
+      for (rp = res; rp; rp = rp->ai_next) {
+        struct evconnlistener *listener;
+        listener = evconnlistener_new_bind(
+            evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
+            16, rp->ai_addr, rp->ai_addrlen);
+        if (listener) {
+          freeaddrinfo(res);
+
+          return;
+        }
+      }
+      errx(1, "Could not start listener");
+    }
+
+We specify the ``acceptcb`` callback which is called when a new connection is
+accepted::
+
+    static void acceptcb(struct evconnlistener *listener _U_, int fd,
+                         struct sockaddr *addr, int addrlen, void *arg) {
+      app_context *app_ctx = (app_context *)arg;
+      http2_session_data *session_data;
+
+      session_data = create_http2_session_data(app_ctx, fd, addr, addrlen);
+
+      bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data);
+    }
+
+Here we create the ``http2_session_data`` object. The bufferevent for
+this connection is also initialized at this time. We specify three
+callbacks for the bufferevent: ``readcb``, ``writecb`` and
+``eventcb``.
+
+The ``eventcb()`` callback is invoked by the libevent event loop when an event
+(e.g., connection has been established, timeout, etc) happens on the
+underlying network socket::
+
+    static void eventcb(struct bufferevent *bev _U_, short events, void *ptr) {
+      http2_session_data *session_data = (http2_session_data *)ptr;
+      if (events & BEV_EVENT_CONNECTED) {
+        fprintf(stderr, "%s connected\n", session_data->client_addr);
+
+        initialize_nghttp2_session(session_data);
+
+        if (send_server_connection_header(session_data) != 0) {
+          delete_http2_session_data(session_data);
+          return;
+        }
+
+        return;
+      }
+      if (events & BEV_EVENT_EOF) {
+        fprintf(stderr, "%s EOF\n", session_data->client_addr);
+      } else if (events & BEV_EVENT_ERROR) {
+        fprintf(stderr, "%s network error\n", session_data->client_addr);
+      } else if (events & BEV_EVENT_TIMEOUT) {
+        fprintf(stderr, "%s timeout\n", session_data->client_addr);
+      }
+      delete_http2_session_data(session_data);
+    }
+
+For the ``BEV_EVENT_EOF``, ``BEV_EVENT_ERROR`` and
+``BEV_EVENT_TIMEOUT`` events, we just simply tear down the connection.
+The ``delete_http2_session_data()`` function destroys the
+``http2_session_data`` object and thus also its bufferevent member.
+As a result, the underlying connection is closed.  The
+``BEV_EVENT_CONNECTED`` event is invoked when SSL/TLS handshake is
+finished successfully.  Now we are ready to start the HTTP/2
+communication.
+
+We initialize a nghttp2 session object which is done in
+``initialize_nghttp2_session()``::
+
+    static void initialize_nghttp2_session(http2_session_data *session_data) {
+      nghttp2_option *option;
+      nghttp2_session_callbacks *callbacks;
+
+      nghttp2_option_new(&option);
+
+      /* Tells nghttp2_session object that it handles client connection
+         preface */
+      nghttp2_option_set_recv_client_preface(option, 1);
+
+      nghttp2_session_callbacks_new(&callbacks);
+
+      nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+      nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                           on_frame_recv_callback);
+
+      nghttp2_session_callbacks_set_on_stream_close_callback(
+          callbacks, on_stream_close_callback);
+
+      nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                       on_header_callback);
+
+      nghttp2_session_callbacks_set_on_begin_headers_callback(
+          callbacks, on_begin_headers_callback);
+
+      nghttp2_session_server_new2(&session_data->session, callbacks, session_data,
+                                  option);
+
+      nghttp2_session_callbacks_del(callbacks);
+      nghttp2_option_del(option);
+    }
+
+Since we are creating a server and uses options, the nghttp2 session
+object is created using `nghttp2_session_server_new2()` function.  We
+registers five callbacks for nghttp2 session object.  We'll talk about
+these callbacks later.  Our server only speaks HTTP/2.  In this case,
+we use `nghttp2_option_set_recv_client_preface()` to make
+:type:`nghttp2_session` object handle client connection preface, which
+saves some lines of application code.
+
+After initialization of the nghttp2 session object, we are going to send
+a server connection header in ``send_server_connection_header()``::
+
+    static int send_server_connection_header(http2_session_data *session_data) {
+      nghttp2_settings_entry iv[1] = {
+          {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+      int rv;
+
+      rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
+                                   ARRLEN(iv));
+      if (rv != 0) {
+        warnx("Fatal error: %s", nghttp2_strerror(rv));
+        return -1;
+      }
+      return 0;
+    }
+
+The server connection header is a SETTINGS frame. We specify
+SETTINGS_MAX_CONCURRENT_STREAMS to 100 in the SETTINGS frame.  To queue
+the SETTINGS frame for the transmission, we use
+`nghttp2_submit_settings()`. Note that `nghttp2_submit_settings()`
+function only queues the frame and it does not actually send it. All
+functions in the ``nghttp2_submit_*()`` family have this property. To
+actually send the frame, `nghttp2_session_send()` should be used, as
+described later.
+
+Since bufferevent may buffer more than the first 24 bytes from the client, we
+have to process them here since libevent won't invoke callback functions for
+this pending data. To process the received data, we call the
+``session_recv()`` function::
+
+    static int session_recv(http2_session_data *session_data) {
+      ssize_t readlen;
+      struct evbuffer *input = bufferevent_get_input(session_data->bev);
+      size_t datalen = evbuffer_get_length(input);
+      unsigned char *data = evbuffer_pullup(input, -1);
+
+      readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
+      if (readlen < 0) {
+        warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
+        return -1;
+      }
+      if (evbuffer_drain(input, readlen) != 0) {
+        warnx("Fatal error: evbuffer_drain failed");
+        return -1;
+      }
+      if (session_send(session_data) != 0) {
+        return -1;
+      }
+      return 0;
+    }
+
+In this function, we feed all unprocessed but already received data to the
+nghttp2 session object using the `nghttp2_session_mem_recv()` function. The
+`nghttp2_session_mem_recv()` function processes the data and may invoke the
+nghttp2 callbacks and also queue outgoing frames. Since there may be pending
+outgoing frames, we call ``session_send()`` function to send off those
+frames. The ``session_send()`` function is defined as follows::
+
+    static int session_send(http2_session_data *session_data) {
+      int rv;
+      rv = nghttp2_session_send(session_data->session);
+      if (rv != 0) {
+        warnx("Fatal error: %s", nghttp2_strerror(rv));
+        return -1;
+      }
+      return 0;
+    }
+
+The `nghttp2_session_send()` function serializes the frame into wire
+format and calls ``send_callback()`` of type
+:type:`nghttp2_send_callback`.  The ``send_callback()`` is defined as
+follows::
+
+    static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
+                                 size_t length, int flags _U_, void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      struct bufferevent *bev = session_data->bev;
+      /* Avoid excessive buffering in server side. */
+      if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >=
+          OUTPUT_WOULDBLOCK_THRESHOLD) {
+        return NGHTTP2_ERR_WOULDBLOCK;
+      }
+      bufferevent_write(bev, data, length);
+      return length;
+    }
+
+Since we use bufferevent to abstract network I/O, we just write the
+data to the bufferevent object. Note that `nghttp2_session_send()`
+continues to write all frames queued so far. If we were writing the
+data to a non-blocking socket directly using ``write()`` system call
+in the ``send_callback()``, we would surely get ``EAGAIN`` or
+``EWOULDBLOCK`` back since the socket has limited send buffer. If that
+happens, we can return :macro:`NGHTTP2_ERR_WOULDBLOCK` to signal the
+nghttp2 library to stop sending further data. But when writing to the
+bufferevent, we have to regulate the amount data to get buffered
+ourselves to avoid using huge amounts of memory. To achieve this, we
+check the size of the output buffer and if it reaches more than or
+equal to ``OUTPUT_WOULDBLOCK_THRESHOLD`` bytes, we stop writing data
+and return :macro:`NGHTTP2_ERR_WOULDBLOCK` to tell the library to stop
+calling send_callback.
+
+The next bufferevent callback is ``readcb()``, which is invoked when
+data is available to read in the bufferevent input buffer::
+
+    static void readcb(struct bufferevent *bev _U_, void *ptr) {
+      http2_session_data *session_data = (http2_session_data *)ptr;
+      if (session_recv(session_data) != 0) {
+        delete_http2_session_data(session_data);
+        return;
+      }
+    }
+
+In this function, we just call ``session_recv()`` to process incoming
+data.
+
+The third bufferevent callback is ``writecb()``, which is invoked when all
+data in the bufferevent output buffer has been sent::
+
+    static void writecb(struct bufferevent *bev, void *ptr) {
+      http2_session_data *session_data = (http2_session_data *)ptr;
+      if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
+        return;
+      }
+      if (nghttp2_session_want_read(session_data->session) == 0 &&
+          nghttp2_session_want_write(session_data->session) == 0) {
+        delete_http2_session_data(session_data);
+        return;
+      }
+      if (session_send(session_data) != 0) {
+        delete_http2_session_data(session_data);
+        return;
+      }
+    }
+
+First we check whether we should drop the connection or not. The nghttp2
+session object keeps track of reception and transmission of GOAWAY frames and
+other error conditions as well. Using this information, the nghttp2 session
+object will tell whether the connection should be dropped or not. More
+specifically, if both `nghttp2_session_want_read()` and
+`nghttp2_session_want_write()` return 0, we have no business left in the
+connection. But since we are using bufferevent and its deferred callback
+option, the bufferevent output buffer may contain pending data when the
+``writecb()`` is called. To handle this, we check whether the output buffer is
+empty or not. If all these conditions are met, we drop connection.
+
+Otherwise, we call ``session_send()`` to process the pending output
+data. Remember that in ``send_callback()``, we must not write all data to
+bufferevent to avoid excessive buffering. We continue processing pending data
+when the output buffer becomes empty.
+
+We have already described the nghttp2 callback ``send_callback()``.  Let's
+learn about the remaining nghttp2 callbacks we setup in
+``initialize_nghttp2_setup()`` function.
+
+The ``on_begin_headers_callback()`` function is invoked when the reception of
+a header block in HEADERS or PUSH_PROMISE frame is started::
+
+    static int on_begin_headers_callback(nghttp2_session *session,
+                                         const nghttp2_frame *frame,
+                                         void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      http2_stream_data *stream_data;
+
+      if (frame->hd.type != NGHTTP2_HEADERS ||
+          frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+        return 0;
+      }
+      stream_data = create_http2_stream_data(session_data, frame->hd.stream_id);
+      nghttp2_session_set_stream_user_data(session, frame->hd.stream_id,
+                                           stream_data);
+      return 0;
+    }
+
+We are only interested in the HEADERS frame in this function. Since the
+HEADERS frame has several roles in the HTTP/2 protocol, we check that it is a
+request HEADERS, which opens new stream. If the frame is a request HEADERS, we
+create a ``http2_stream_data`` object to store the stream related data. We
+associate the created ``http2_stream_data`` object with the stream in the
+nghttp2 session object using `nghttp2_set_stream_user_data()` to get the
+object without searching through the doubly linked list.
+
+In this example server, we want to serve files relative to the current working
+directory in which the program was invoked. Each header name/value pair is
+emitted via ``on_header_callback`` function, which is called after
+``on_begin_headers_callback()``::
+
+    static int on_header_callback(nghttp2_session *session,
+                                  const nghttp2_frame *frame, const uint8_t *name,
+                                  size_t namelen, const uint8_t *value,
+                                  size_t valuelen, uint8_t flags _U_,
+                                  void *user_data _U_) {
+      http2_stream_data *stream_data;
+      const char PATH[] = ":path";
+      switch (frame->hd.type) {
+      case NGHTTP2_HEADERS:
+        if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+          break;
+        }
+        stream_data =
+            nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+        if (!stream_data || stream_data->request_path) {
+          break;
+        }
+        if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) {
+          size_t j;
+          for (j = 0; j < valuelen && value[j] != '?'; ++j)
+            ;
+          stream_data->request_path = percent_decode(value, j);
+        }
+        break;
+      }
+      return 0;
+    }
+
+We search for the ``:path`` header field among the request headers and store
+the requested path in the ``http2_stream_data`` object. In this example
+program, we ignore ``:method`` header field and always treat the request as a
+GET request.
+
+The ``on_frame_recv_callback()`` function is invoked when a frame is
+fully received::
+
+    static int on_frame_recv_callback(nghttp2_session *session,
+                                      const nghttp2_frame *frame, void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      http2_stream_data *stream_data;
+      switch (frame->hd.type) {
+      case NGHTTP2_DATA:
+      case NGHTTP2_HEADERS:
+        /* Check that the client request has finished */
+        if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+          stream_data =
+              nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+          /* For DATA and HEADERS frame, this callback may be called after
+             on_stream_close_callback. Check that stream still alive. */
+          if (!stream_data) {
+            return 0;
+          }
+          return on_request_recv(session, session_data, stream_data);
+        }
+        break;
+      default:
+        break;
+      }
+      return 0;
+    }
+
+First we retrieve the ``http2_stream_data`` object associated with the stream
+in ``on_begin_headers_callback()``. It is done using
+`nghttp2_session_get_stream_user_data()`. If the requested path cannot be
+served for some reason (e.g., file is not found), we send a 404 response,
+which is done in ``error_reply()``.  Otherwise, we open the requested file and
+send its content. We send the header field ``:status`` as a single response
+header.
+
+Sending the content of the file is done in ``send_response()`` function::
+
+    static int send_response(nghttp2_session *session, int32_t stream_id,
+                             nghttp2_nv *nva, size_t nvlen, int fd) {
+      int rv;
+      nghttp2_data_provider data_prd;
+      data_prd.source.fd = fd;
+      data_prd.read_callback = file_read_callback;
+
+      rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd);
+      if (rv != 0) {
+        warnx("Fatal error: %s", nghttp2_strerror(rv));
+        return -1;
+      }
+      return 0;
+    }
+
+The nghttp2 library uses the :type:`nghttp2_data_provider` structure to
+send entity body to the remote peer. The ``source`` member of this
+structure is a union and it can be either void pointer or int which is
+intended to be used as file descriptor. In this example server, we use
+the file descriptor. We also set the ``file_read_callback()`` callback
+function to read the contents of the file::
+
+    static ssize_t file_read_callback(nghttp2_session *session _U_,
+                                      int32_t stream_id _U_, uint8_t *buf,
+                                      size_t length, uint32_t *data_flags,
+                                      nghttp2_data_source *source,
+                                      void *user_data _U_) {
+      int fd = source->fd;
+      ssize_t r;
+      while ((r = read(fd, buf, length)) == -1 && errno == EINTR)
+        ;
+      if (r == -1) {
+        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+      }
+      if (r == 0) {
+        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+      }
+      return r;
+    }
+
+If an error happens while reading the file, we return
+:macro:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.  This tells the
+library to send RST_STREAM to the stream.  When all data has been read, set
+the :macro:`NGHTTP2_DATA_FLAG_EOF` flag to ``*data_flags`` to tell the
+nghttp2 library that we have finished reading the file.
+
+The `nghttp2_submit_response()` function is used to send the response to the
+remote peer.
+
+The ``on_stream_close_callback()`` function is invoked when the stream
+is about to close::
+
+    static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                                        uint32_t error_code _U_, void *user_data) {
+      http2_session_data *session_data = (http2_session_data *)user_data;
+      http2_stream_data *stream_data;
+
+      stream_data = nghttp2_session_get_stream_user_data(session, stream_id);
+      if (!stream_data) {
+        return 0;
+      }
+      remove_stream(session_data, stream_data);
+      delete_http2_stream_data(stream_data);
+      return 0;
+    }
+
+We destroy the ``http2_stream_data`` object in this function since the stream
+is about to close and we no longer use that object.
diff --git a/doc/tutorial-client.rst.in b/doc/tutorial-client.rst.in
new file mode 100644 (file)
index 0000000..4f7fcfc
--- /dev/null
@@ -0,0 +1,6 @@
+.. include:: @top_srcdir@/doc/sources/tutorial-client.rst
+
+libevent-client.c
+-----------------
+
+.. literalinclude:: @top_srcdir@/examples/libevent-client.c
diff --git a/doc/tutorial-hpack.rst.in b/doc/tutorial-hpack.rst.in
new file mode 100644 (file)
index 0000000..832dedf
--- /dev/null
@@ -0,0 +1,6 @@
+.. include:: @top_srcdir@/doc/sources/tutorial-hpack.rst
+
+deflate.c
+---------
+
+.. literalinclude:: @top_srcdir@/examples/deflate.c
diff --git a/doc/tutorial-server.rst.in b/doc/tutorial-server.rst.in
new file mode 100644 (file)
index 0000000..1972266
--- /dev/null
@@ -0,0 +1,6 @@
+.. include:: @top_srcdir@/doc/sources/tutorial-server.rst
+
+libevent-server.c
+-----------------
+
+.. literalinclude:: @top_srcdir@/examples/libevent-server.c
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644 (file)
index 0000000..8846c28
--- /dev/null
@@ -0,0 +1,8 @@
+client
+libevent-client
+libevent-server
+deflate
+asio-sv
+tiny-nghttpd
+asio-sv2
+asio-sv3
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644 (file)
index 0000000..9412099
--- /dev/null
@@ -0,0 +1,80 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+if ENABLE_EXAMPLES
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CPPFLAGS = \
+       -Wall \
+       -I$(top_srcdir)/lib/includes \
+       -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/src/includes \
+       -I$(top_srcdir)/third-party \
+       @LIBEVENT_OPENSSL_CFLAGS@ \
+       @OPENSSL_CFLAGS@ \
+       @DEFS@
+LDADD = $(top_builddir)/lib/libnghttp2.la \
+       $(top_builddir)/third-party/libhttp-parser.la \
+       @LIBEVENT_OPENSSL_LIBS@ \
+       @OPENSSL_LIBS@
+
+noinst_PROGRAMS = client libevent-client libevent-server deflate
+
+client_SOURCES = client.c
+
+libevent_client_SOURCES = libevent-client.c
+
+libevent_server_SOURCES = libevent-server.c
+
+deflate_SOURCES = deflate.c
+
+if ENABLE_TINY_NGHTTPD
+
+noinst_PROGRAMS += tiny-nghttpd
+
+tiny_nghttpd_SOURCES = tiny-nghttpd.c
+
+endif # ENABLE_TINY_NGHTTPD
+
+if ENABLE_ASIO_LIB
+
+noinst_PROGRAMS += asio-sv asio-sv2 asio-sv3
+
+ASIOCPPFLAGS = ${BOOST_CPPFLAGS} ${AM_CPPFLAGS}
+ASIOLDADD = $(top_builddir)/src/libnghttp2_asio.la @JEMALLOC_LIBS@
+
+asio_sv_SOURCES = asio-sv.cc
+asio_sv_CPPFLAGS = ${ASIOCPPFLAGS}
+asio_sv_LDADD = ${ASIOLDADD}
+
+asio_sv2_SOURCES = asio-sv2.cc
+asio_sv2_CPPFLAGS = ${ASIOCPPFLAGS}
+asio_sv2_LDADD = ${ASIOLDADD}
+
+asio_sv3_SOURCES = asio-sv3.cc
+asio_sv3_CPPFLAGS = ${ASIOCPPFLAGS}
+asio_sv3_LDADD = ${ASIOLDADD}
+
+endif # ENABLE_ASIO_LIB
+
+endif # ENABLE_EXAMPLES
diff --git a/examples/asio-sv.cc b/examples/asio-sv.cc
new file mode 100644 (file)
index 0000000..9c98b02
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// main.cpp
+// ~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include <iostream>
+#include <string>
+
+#include <nghttp2/asio_http2.h>
+
+using namespace nghttp2::asio_http2;
+using namespace nghttp2::asio_http2::server;
+
+int main(int argc, char *argv[]) {
+  try {
+    // Check command line arguments.
+    if (argc < 3) {
+      std::cerr << "Usage: asio-sv <port> <threads> <private-key-file> "
+                << "<cert-file>\n";
+      return 1;
+    }
+
+    uint16_t port = std::stoi(argv[1]);
+    std::size_t num_threads = std::stoi(argv[2]);
+
+    http2 server;
+
+    server.num_threads(num_threads);
+
+    if (argc >= 5) {
+      server.tls(argv[3], argv[4]);
+    }
+
+    server.listen("*", port, [](const std::shared_ptr<request> &req,
+                                const std::shared_ptr<response> &res) {
+      res->write_head(200, {header{"foo", "bar"}});
+      res->end("hello, world");
+    });
+  } catch (std::exception &e) {
+    std::cerr << "exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
diff --git a/examples/asio-sv2.cc b/examples/asio-sv2.cc
new file mode 100644 (file)
index 0000000..bbade63
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// main.cpp
+// ~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <iostream>
+#include <string>
+
+#include <nghttp2/asio_http2.h>
+
+using namespace nghttp2::asio_http2;
+using namespace nghttp2::asio_http2::server;
+
+int main(int argc, char *argv[]) {
+  try {
+    // Check command line arguments.
+    if (argc < 4) {
+      std::cerr << "Usage: asio-sv2 <port> <threads> <doc-root> "
+                << "<private-key-file> <cert-file>\n";
+      return 1;
+    }
+
+    uint16_t port = std::stoi(argv[1]);
+    std::size_t num_threads = std::stoi(argv[2]);
+    std::string docroot = argv[3];
+
+    http2 server;
+
+    server.num_threads(num_threads);
+
+    if (argc >= 6) {
+      server.tls(argv[4], argv[5]);
+    }
+
+    server.listen("*", port, [&docroot](const std::shared_ptr<request> &req,
+                                        const std::shared_ptr<response> &res) {
+      auto path = percent_decode(req->path());
+      if (!check_path(path)) {
+        res->write_head(404);
+        res->end();
+        return;
+      }
+
+      if (path == "/") {
+        path = "/index.html";
+      }
+
+      path = docroot + path;
+      auto fd = open(path.c_str(), O_RDONLY);
+      if (fd == -1) {
+        res->write_head(404);
+        res->end();
+        return;
+      }
+
+      auto headers = std::vector<header>();
+
+      struct stat stbuf;
+      if (stat(path.c_str(), &stbuf) == 0) {
+        headers.push_back(
+            header{"content-length", std::to_string(stbuf.st_size)});
+        headers.push_back(
+            header{"last-modified", http_date(stbuf.st_mtim.tv_sec)});
+      }
+      res->write_head(200, std::move(headers));
+      res->end(file_reader_from_fd(fd));
+    });
+  } catch (std::exception &e) {
+    std::cerr << "exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
diff --git a/examples/asio-sv3.cc b/examples/asio-sv3.cc
new file mode 100644 (file)
index 0000000..66a5e2b
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// main.cpp
+// ~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include <unistd.h>
+#include <iostream>
+#include <string>
+#include <deque>
+
+#include <nghttp2/asio_http2.h>
+
+using namespace nghttp2::asio_http2;
+using namespace nghttp2::asio_http2::server;
+
+int main(int argc, char *argv[]) {
+  try {
+    // Check command line arguments.
+    if (argc < 4) {
+      std::cerr << "Usage: asio-sv3 <port> <threads> <tasks> "
+                << " <private-key-file> <cert-file>\n";
+      return 1;
+    }
+
+    uint16_t port = std::stoi(argv[1]);
+    std::size_t num_threads = std::stoi(argv[2]);
+    std::size_t num_concurrent_tasks = std::stoi(argv[3]);
+
+    http2 server;
+
+    server.num_threads(num_threads);
+
+    if (argc >= 5) {
+      server.tls(argv[4], argv[5]);
+    }
+
+    server.num_concurrent_tasks(num_concurrent_tasks);
+
+    server.listen("*", port, [](const std::shared_ptr<request> &req,
+                                const std::shared_ptr<response> &res) {
+      res->write_head(200);
+
+      auto msgq = std::make_shared<std::deque<std::string>>();
+
+      res->end([msgq](uint8_t * buf, std::size_t len)
+                   -> std::pair<ssize_t, bool> {
+        if (msgq->empty()) {
+          // if msgq is empty, tells the library that don't call
+          // this callback until we call res->resume().  This is
+          // done by returing std::make_pair(0, false).
+          return std::make_pair(0, false);
+        }
+        auto msg = std::move(msgq->front());
+        msgq->pop_front();
+
+        if (msg.empty()) {
+          // The empty message signals the end of response in
+          // this simple protocol.
+          return std::make_pair(0, true);
+        }
+
+        auto nwrite = std::min(len, msg.size());
+        std::copy(std::begin(msg), std::begin(msg) + nwrite, buf);
+        if (msg.size() > nwrite) {
+          msgq->push_front(msg.substr(nwrite));
+        }
+        return std::make_pair(nwrite, false);
+      });
+
+      req->run_task([res, msgq](channel &channel) {
+        // executed in different thread from request callback
+        // was called.
+
+        // Using res and msgq is not safe inside this callback.
+        // But using them in callback passed to channel::post is
+        // safe.
+
+        // We just emit simple message "message N\n" in every 1
+        // second and 3 times in total.
+        for (std::size_t i = 0; i < 3; ++i) {
+          msgq->push_back("message " + std::to_string(i + 1) + "\n");
+
+          channel.post([res]() {
+            // executed in same thread where
+            // request callback was called.
+
+            // Tells library we have new message.
+            res->resume();
+          });
+
+          sleep(1);
+        }
+
+        // Send empty message to signal the end of response
+        // body.
+        msgq->push_back("");
+
+        channel.post([res]() {
+          // executed in same thread where request
+          // callback was called.
+          res->resume();
+        });
+
+      });
+
+    });
+  } catch (std::exception &e) {
+    std::cerr << "exception: " << e.what() << "\n";
+  }
+
+  return 0;
+}
diff --git a/examples/client.c b/examples/client.c
new file mode 100644 (file)
index 0000000..314efb3
--- /dev/null
@@ -0,0 +1,701 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * This program is written to show how to use nghttp2 API in C and
+ * intentionally made simple.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* !HAVE_CONFIG_H */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+enum { IO_NONE, WANT_READ, WANT_WRITE };
+
+#define MAKE_NV(NAME, VALUE)                                                   \
+  {                                                                            \
+    (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1,   \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+#define MAKE_NV_CS(NAME, VALUE)                                                \
+  {                                                                            \
+    (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, strlen(VALUE),       \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+struct Connection {
+  SSL *ssl;
+  nghttp2_session *session;
+  /* WANT_READ if SSL/TLS connection needs more input; or WANT_WRITE
+     if it needs more output; or IO_NONE. This is necessary because
+     SSL/TLS re-negotiation is possible at any time. nghttp2 API
+     offers similar functions like nghttp2_session_want_read() and
+     nghttp2_session_want_write() but they do not take into account
+     SSL/TSL connection. */
+  int want_io;
+};
+
+struct Request {
+  char *host;
+  /* In this program, path contains query component as well. */
+  char *path;
+  /* This is the concatenation of host and port with ":" in
+     between. */
+  char *hostport;
+  /* Stream ID for this request. */
+  int32_t stream_id;
+  uint16_t port;
+};
+
+struct URI {
+  const char *host;
+  /* In this program, path contains query component as well. */
+  const char *path;
+  size_t pathlen;
+  const char *hostport;
+  size_t hostlen;
+  size_t hostportlen;
+  uint16_t port;
+};
+
+/*
+ * Returns copy of string |s| with the length |len|. The returned
+ * string is NULL-terminated.
+ */
+static char *strcopy(const char *s, size_t len) {
+  char *dst;
+  dst = malloc(len + 1);
+  memcpy(dst, s, len);
+  dst[len] = '\0';
+  return dst;
+}
+
+/*
+ * Prints error message |msg| and exit.
+ */
+static void die(const char *msg) {
+  fprintf(stderr, "FATAL: %s\n", msg);
+  exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and message |msg|
+ * and exit.
+ */
+static void dief(const char *func, const char *msg) {
+  fprintf(stderr, "FATAL: %s: %s\n", func, msg);
+  exit(EXIT_FAILURE);
+}
+
+/*
+ * Prints error containing the function name |func| and error code
+ * |error_code| and exit.
+ */
+static void diec(const char *func, int error_code) {
+  fprintf(stderr, "FATAL: %s: error_code=%d, msg=%s\n", func, error_code,
+          nghttp2_strerror(error_code));
+  exit(EXIT_FAILURE);
+}
+
+/*
+ * The implementation of nghttp2_send_callback type. Here we write
+ * |data| with size |length| to the network and return the number of
+ * bytes actually written. See the documentation of
+ * nghttp2_send_callback for the details.
+ */
+static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
+                             size_t length, int flags _U_, void *user_data) {
+  struct Connection *connection;
+  int rv;
+  connection = (struct Connection *)user_data;
+  connection->want_io = IO_NONE;
+  ERR_clear_error();
+  rv = SSL_write(connection->ssl, data, (int)length);
+  if (rv <= 0) {
+    int err = SSL_get_error(connection->ssl, rv);
+    if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+      connection->want_io =
+          (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
+      rv = NGHTTP2_ERR_WOULDBLOCK;
+    } else {
+      rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return rv;
+}
+
+/*
+ * The implementation of nghttp2_recv_callback type. Here we read data
+ * from the network and write them in |buf|. The capacity of |buf| is
+ * |length| bytes. Returns the number of bytes stored in |buf|. See
+ * the documentation of nghttp2_recv_callback for the details.
+ */
+static ssize_t recv_callback(nghttp2_session *session _U_, uint8_t *buf,
+                             size_t length, int flags _U_, void *user_data) {
+  struct Connection *connection;
+  int rv;
+  connection = (struct Connection *)user_data;
+  connection->want_io = IO_NONE;
+  ERR_clear_error();
+  rv = SSL_read(connection->ssl, buf, (int)length);
+  if (rv < 0) {
+    int err = SSL_get_error(connection->ssl, rv);
+    if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
+      connection->want_io =
+          (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
+      rv = NGHTTP2_ERR_WOULDBLOCK;
+    } else {
+      rv = NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  } else if (rv == 0) {
+    rv = NGHTTP2_ERR_EOF;
+  }
+  return rv;
+}
+
+static int on_frame_send_callback(nghttp2_session *session,
+                                  const nghttp2_frame *frame,
+                                  void *user_data _U_) {
+  size_t i;
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
+      const nghttp2_nv *nva = frame->headers.nva;
+      printf("[INFO] C ----------------------------> S (HEADERS)\n");
+      for (i = 0; i < frame->headers.nvlen; ++i) {
+        fwrite(nva[i].name, nva[i].namelen, 1, stdout);
+        printf(": ");
+        fwrite(nva[i].value, nva[i].valuelen, 1, stdout);
+        printf("\n");
+      }
+    }
+    break;
+  case NGHTTP2_RST_STREAM:
+    printf("[INFO] C ----------------------------> S (RST_STREAM)\n");
+    break;
+  case NGHTTP2_GOAWAY:
+    printf("[INFO] C ----------------------------> S (GOAWAY)\n");
+    break;
+  }
+  return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+                                  const nghttp2_frame *frame,
+                                  void *user_data _U_) {
+  size_t i;
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
+      const nghttp2_nv *nva = frame->headers.nva;
+      struct Request *req;
+      req = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+      if (req) {
+        printf("[INFO] C <---------------------------- S (HEADERS)\n");
+        for (i = 0; i < frame->headers.nvlen; ++i) {
+          fwrite(nva[i].name, nva[i].namelen, 1, stdout);
+          printf(": ");
+          fwrite(nva[i].value, nva[i].valuelen, 1, stdout);
+          printf("\n");
+        }
+      }
+    }
+    break;
+  case NGHTTP2_RST_STREAM:
+    printf("[INFO] C <---------------------------- S (RST_STREAM)\n");
+    break;
+  case NGHTTP2_GOAWAY:
+    printf("[INFO] C <---------------------------- S (GOAWAY)\n");
+    break;
+  }
+  return 0;
+}
+
+/*
+ * The implementation of nghttp2_on_stream_close_callback type. We use
+ * this function to know the response is fully received. Since we just
+ * fetch 1 resource in this program, after reception of the response,
+ * we submit GOAWAY and close the session.
+ */
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                                    uint32_t error_code _U_,
+                                    void *user_data _U_) {
+  struct Request *req;
+  req = nghttp2_session_get_stream_user_data(session, stream_id);
+  if (req) {
+    int rv;
+    rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+
+    if (rv != 0) {
+      diec("nghttp2_session_terminate_session", rv);
+    }
+  }
+  return 0;
+}
+
+#define MAX_OUTLEN 4096
+
+/*
+ * The implementation of nghttp2_on_data_chunk_recv_callback type. We
+ * use this function to print the received response body.
+ */
+static int on_data_chunk_recv_callback(nghttp2_session *session,
+                                       uint8_t flags _U_, int32_t stream_id,
+                                       const uint8_t *data, size_t len,
+                                       void *user_data _U_) {
+  struct Request *req;
+  req = nghttp2_session_get_stream_user_data(session, stream_id);
+  if (req) {
+    printf("[INFO] C <---------------------------- S (DATA chunk)\n"
+           "%lu bytes\n",
+           (unsigned long int)len);
+    fwrite(data, 1, len, stdout);
+    printf("\n");
+  }
+  return 0;
+}
+
+/*
+ * Setup callback functions. nghttp2 API offers many callback
+ * functions, but most of them are optional. The send_callback is
+ * always required. Since we use nghttp2_session_recv(), the
+ * recv_callback is also required.
+ */
+static void setup_nghttp2_callbacks(nghttp2_session_callbacks *callbacks) {
+  nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+  nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
+
+  nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+                                                       on_frame_send_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+}
+
+/*
+ * Callback function for TLS NPN. Since this program only supports
+ * HTTP/2 protocol, if server does not offer HTTP/2 the nghttp2
+ * library supports, we terminate program.
+ */
+static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
+                                unsigned char *outlen, const unsigned char *in,
+                                unsigned int inlen, void *arg _U_) {
+  int rv;
+  /* nghttp2_select_next_protocol() selects HTTP/2 protocol the
+     nghttp2 library supports. */
+  rv = nghttp2_select_next_protocol(out, outlen, in, inlen);
+  if (rv <= 0) {
+    die("Server did not advertise HTTP/2 protocol");
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+
+/*
+ * Setup SSL/TLS context.
+ */
+static void init_ssl_ctx(SSL_CTX *ssl_ctx) {
+  /* Disable SSLv2 and enable all workarounds for buggy servers */
+  SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+  /* Set NPN callback */
+  SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
+}
+
+static void ssl_handshake(SSL *ssl, int fd) {
+  int rv;
+  if (SSL_set_fd(ssl, fd) == 0) {
+    dief("SSL_set_fd", ERR_error_string(ERR_get_error(), NULL));
+  }
+  ERR_clear_error();
+  rv = SSL_connect(ssl);
+  if (rv <= 0) {
+    dief("SSL_connect", ERR_error_string(ERR_get_error(), NULL));
+  }
+}
+
+/*
+ * Connects to the host |host| and port |port|.  This function returns
+ * the file descriptor of the client socket.
+ */
+static int connect_to(const char *host, uint16_t port) {
+  struct addrinfo hints;
+  int fd = -1;
+  int rv;
+  char service[NI_MAXSERV];
+  struct addrinfo *res, *rp;
+  snprintf(service, sizeof(service), "%u", port);
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  rv = getaddrinfo(host, service, &hints, &res);
+  if (rv != 0) {
+    dief("getaddrinfo", gai_strerror(rv));
+  }
+  for (rp = res; rp; rp = rp->ai_next) {
+    fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    if (fd == -1) {
+      continue;
+    }
+    while ((rv = connect(fd, rp->ai_addr, rp->ai_addrlen)) == -1 &&
+           errno == EINTR)
+      ;
+    if (rv == 0) {
+      break;
+    }
+    close(fd);
+    fd = -1;
+  }
+  freeaddrinfo(res);
+  return fd;
+}
+
+static void make_non_block(int fd) {
+  int flags, rv;
+  while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
+    ;
+  if (flags == -1) {
+    dief("fcntl", strerror(errno));
+  }
+  while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
+    ;
+  if (rv == -1) {
+    dief("fcntl", strerror(errno));
+  }
+}
+
+static void set_tcp_nodelay(int fd) {
+  int val = 1;
+  int rv;
+  rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, (socklen_t)sizeof(val));
+  if (rv == -1) {
+    dief("setsockopt", strerror(errno));
+  }
+}
+
+/*
+ * Update |pollfd| based on the state of |connection|.
+ */
+static void ctl_poll(struct pollfd *pollfd, struct Connection *connection) {
+  pollfd->events = 0;
+  if (nghttp2_session_want_read(connection->session) ||
+      connection->want_io == WANT_READ) {
+    pollfd->events |= POLLIN;
+  }
+  if (nghttp2_session_want_write(connection->session) ||
+      connection->want_io == WANT_WRITE) {
+    pollfd->events |= POLLOUT;
+  }
+}
+
+/*
+ * Submits the request |req| to the connection |connection|.  This
+ * function does not send packets; just append the request to the
+ * internal queue in |connection->session|.
+ */
+static void submit_request(struct Connection *connection, struct Request *req) {
+  int32_t stream_id;
+  /* Make sure that the last item is NULL */
+  const nghttp2_nv nva[] = {MAKE_NV(":method", "GET"),
+                            MAKE_NV_CS(":path", req->path),
+                            MAKE_NV(":scheme", "https"),
+                            MAKE_NV_CS(":authority", req->hostport),
+                            MAKE_NV("accept", "*/*"),
+                            MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)};
+
+  stream_id = nghttp2_submit_request(connection->session, NULL, nva,
+                                     sizeof(nva) / sizeof(nva[0]), NULL, req);
+
+  if (stream_id < 0) {
+    diec("nghttp2_submit_request", stream_id);
+  }
+
+  req->stream_id = stream_id;
+  printf("[INFO] Stream ID = %d\n", stream_id);
+}
+
+/*
+ * Performs the network I/O.
+ */
+static void exec_io(struct Connection *connection) {
+  int rv;
+  rv = nghttp2_session_recv(connection->session);
+  if (rv != 0) {
+    diec("nghttp2_session_recv", rv);
+  }
+  rv = nghttp2_session_send(connection->session);
+  if (rv != 0) {
+    diec("nghttp2_session_send", rv);
+  }
+}
+
+static void request_init(struct Request *req, const struct URI *uri) {
+  req->host = strcopy(uri->host, uri->hostlen);
+  req->port = uri->port;
+  req->path = strcopy(uri->path, uri->pathlen);
+  req->hostport = strcopy(uri->hostport, uri->hostportlen);
+  req->stream_id = -1;
+}
+
+static void request_free(struct Request *req) {
+  free(req->host);
+  free(req->path);
+  free(req->hostport);
+}
+
+/*
+ * Fetches the resource denoted by |uri|.
+ */
+static void fetch_uri(const struct URI *uri) {
+  nghttp2_session_callbacks *callbacks;
+  int fd;
+  SSL_CTX *ssl_ctx;
+  SSL *ssl;
+  struct Request req;
+  struct Connection connection;
+  int rv;
+  nfds_t npollfds = 1;
+  struct pollfd pollfds[1];
+
+  request_init(&req, uri);
+
+  /* Establish connection and setup SSL */
+  fd = connect_to(req.host, req.port);
+  if (fd == -1) {
+    die("Could not open file descriptor");
+  }
+  ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+  if (ssl_ctx == NULL) {
+    dief("SSL_CTX_new", ERR_error_string(ERR_get_error(), NULL));
+  }
+  init_ssl_ctx(ssl_ctx);
+  ssl = SSL_new(ssl_ctx);
+  if (ssl == NULL) {
+    dief("SSL_new", ERR_error_string(ERR_get_error(), NULL));
+  }
+  /* To simplify the program, we perform SSL/TLS handshake in blocking
+     I/O. */
+  ssl_handshake(ssl, fd);
+
+  connection.ssl = ssl;
+  connection.want_io = IO_NONE;
+
+  /* Send connection header in blocking I/O mode */
+  rv = SSL_write(ssl, NGHTTP2_CLIENT_CONNECTION_PREFACE,
+                 NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+  if (rv <= 0) {
+    dief("SSL_write failed: could not send connection preface",
+         ERR_error_string(ERR_get_error(), NULL));
+  }
+
+  /* Here make file descriptor non-block */
+  make_non_block(fd);
+  set_tcp_nodelay(fd);
+
+  printf("[INFO] SSL/TLS handshake completed\n");
+
+  rv = nghttp2_session_callbacks_new(&callbacks);
+
+  if (rv != 0) {
+    diec("nghttp2_session_callbacks_new", rv);
+  }
+
+  setup_nghttp2_callbacks(callbacks);
+
+  rv = nghttp2_session_client_new(&connection.session, callbacks, &connection);
+
+  nghttp2_session_callbacks_del(callbacks);
+
+  if (rv != 0) {
+    diec("nghttp2_session_client_new", rv);
+  }
+
+  nghttp2_submit_settings(connection.session, NGHTTP2_FLAG_NONE, NULL, 0);
+
+  /* Submit the HTTP request to the outbound queue. */
+  submit_request(&connection, &req);
+
+  pollfds[0].fd = fd;
+  ctl_poll(pollfds, &connection);
+
+  /* Event loop */
+  while (nghttp2_session_want_read(connection.session) ||
+         nghttp2_session_want_write(connection.session)) {
+    int nfds = poll(pollfds, npollfds, -1);
+    if (nfds == -1) {
+      dief("poll", strerror(errno));
+    }
+    if (pollfds[0].revents & (POLLIN | POLLOUT)) {
+      exec_io(&connection);
+    }
+    if ((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) {
+      die("Connection error");
+    }
+    ctl_poll(pollfds, &connection);
+  }
+
+  /* Resource cleanup */
+  nghttp2_session_del(connection.session);
+  SSL_shutdown(ssl);
+  SSL_free(ssl);
+  SSL_CTX_free(ssl_ctx);
+  shutdown(fd, SHUT_WR);
+  close(fd);
+  request_free(&req);
+}
+
+static int parse_uri(struct URI *res, const char *uri) {
+  /* We only interested in https */
+  size_t len, i, offset;
+  int ipv6addr = 0;
+  memset(res, 0, sizeof(struct URI));
+  len = strlen(uri);
+  if (len < 9 || memcmp("https://", uri, 8) != 0) {
+    return -1;
+  }
+  offset = 8;
+  res->host = res->hostport = &uri[offset];
+  res->hostlen = 0;
+  if (uri[offset] == '[') {
+    /* IPv6 literal address */
+    ++offset;
+    ++res->host;
+    ipv6addr = 1;
+    for (i = offset; i < len; ++i) {
+      if (uri[i] == ']') {
+        res->hostlen = i - offset;
+        offset = i + 1;
+        break;
+      }
+    }
+  } else {
+    const char delims[] = ":/?#";
+    for (i = offset; i < len; ++i) {
+      if (strchr(delims, uri[i]) != NULL) {
+        break;
+      }
+    }
+    res->hostlen = i - offset;
+    offset = i;
+  }
+  if (res->hostlen == 0) {
+    return -1;
+  }
+  /* Assuming https */
+  res->port = 443;
+  if (offset < len) {
+    if (uri[offset] == ':') {
+      /* port */
+      const char delims[] = "/?#";
+      int port = 0;
+      ++offset;
+      for (i = offset; i < len; ++i) {
+        if (strchr(delims, uri[i]) != NULL) {
+          break;
+        }
+        if ('0' <= uri[i] && uri[i] <= '9') {
+          port *= 10;
+          port += uri[i] - '0';
+          if (port > 65535) {
+            return -1;
+          }
+        } else {
+          return -1;
+        }
+      }
+      if (port == 0) {
+        return -1;
+      }
+      offset = i;
+      res->port = port;
+    }
+  }
+  res->hostportlen = uri + offset + ipv6addr - res->host;
+  for (i = offset; i < len; ++i) {
+    if (uri[i] == '#') {
+      break;
+    }
+  }
+  if (i - offset == 0) {
+    res->path = "/";
+    res->pathlen = 1;
+  } else {
+    res->path = &uri[offset];
+    res->pathlen = i - offset;
+  }
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  struct URI uri;
+  struct sigaction act;
+  int rv;
+
+  if (argc < 2) {
+    die("Specify a https URI");
+  }
+
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, 0);
+
+  OPENSSL_config(NULL);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  rv = parse_uri(&uri, argv[1]);
+  if (rv != 0) {
+    die("parse_uri failed");
+  }
+  fetch_uri(&uri);
+  return EXIT_SUCCESS;
+}
diff --git a/examples/deflate.c b/examples/deflate.c
new file mode 100644 (file)
index 0000000..d0e7d3f
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* !HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <nghttp2/nghttp2.h>
+
+#define MAKE_NV(K, V)                                                          \
+  {                                                                            \
+    (uint8_t *) K, (uint8_t *)V, sizeof(K) - 1, sizeof(V) - 1,                 \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+static void deflate(nghttp2_hd_deflater *deflater,
+                    nghttp2_hd_inflater *inflater, const nghttp2_nv *const nva,
+                    size_t nvlen);
+
+static int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in,
+                                size_t inlen, int final);
+
+int main(int argc _U_, char **argv _U_) {
+  int rv;
+  nghttp2_hd_deflater *deflater;
+  nghttp2_hd_inflater *inflater;
+  /* Define 1st header set.  This is looks like a HTTP request. */
+  nghttp2_nv nva1[] = {
+      MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"),
+      MAKE_NV(":path", "/"), MAKE_NV("user-agent", "libnghttp2"),
+      MAKE_NV("accept-encoding", "gzip, deflate")};
+  /* Define 2nd header set */
+  nghttp2_nv nva2[] = {MAKE_NV(":scheme", "https"),
+                       MAKE_NV(":authority", "example.org"),
+                       MAKE_NV(":path", "/stylesheet/style.css"),
+                       MAKE_NV("user-agent", "libnghttp2"),
+                       MAKE_NV("accept-encoding", "gzip, deflate"),
+                       MAKE_NV("referer", "https://example.org")};
+
+  rv = nghttp2_hd_deflate_new(&deflater, 4096);
+
+  if (rv != 0) {
+    fprintf(stderr, "nghttp2_hd_deflate_init failed with error: %s\n",
+            nghttp2_strerror(rv));
+    exit(EXIT_FAILURE);
+  }
+
+  rv = nghttp2_hd_inflate_new(&inflater);
+
+  if (rv != 0) {
+    fprintf(stderr, "nghttp2_hd_inflate_init failed with error: %s\n",
+            nghttp2_strerror(rv));
+    exit(EXIT_FAILURE);
+  }
+
+  /* Encode and decode 1st header set */
+  deflate(deflater, inflater, nva1, sizeof(nva1) / sizeof(nva1[0]));
+
+  /* Encode and decode 2nd header set, using differential encoding
+     using state after encoding 1st header set. */
+  deflate(deflater, inflater, nva2, sizeof(nva2) / sizeof(nva2[0]));
+
+  nghttp2_hd_inflate_del(inflater);
+  nghttp2_hd_deflate_del(deflater);
+
+  return 0;
+}
+
+static void deflate(nghttp2_hd_deflater *deflater,
+                    nghttp2_hd_inflater *inflater, const nghttp2_nv *const nva,
+                    size_t nvlen) {
+  ssize_t rv;
+  uint8_t *buf;
+  size_t buflen;
+  size_t outlen;
+  size_t i;
+  size_t sum;
+
+  sum = 0;
+
+  for (i = 0; i < nvlen; ++i) {
+    sum += nva[i].namelen + nva[i].valuelen;
+  }
+
+  printf("Input (%zu byte(s)):\n\n", sum);
+
+  for (i = 0; i < nvlen; ++i) {
+    fwrite(nva[i].name, nva[i].namelen, 1, stdout);
+    printf(": ");
+    fwrite(nva[i].value, nva[i].valuelen, 1, stdout);
+    printf("\n");
+  }
+
+  buflen = nghttp2_hd_deflate_bound(deflater, nva, nvlen);
+  buf = malloc(buflen);
+
+  rv = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, nvlen);
+
+  if (rv < 0) {
+    fprintf(stderr, "nghttp2_hd_deflate_hd() failed with error: %s\n",
+            nghttp2_strerror((int)rv));
+
+    free(buf);
+
+    exit(EXIT_FAILURE);
+  }
+
+  outlen = rv;
+
+  printf("\nDeflate (%zu byte(s), ratio %.02f):\n\n", outlen,
+         sum == 0 ? 0 : (double)outlen / sum);
+
+  for (i = 0; i < outlen; ++i) {
+    if ((i & 0x0fu) == 0) {
+      printf("%08zX: ", i);
+    }
+
+    printf("%02X ", buf[i]);
+
+    if (((i + 1) & 0x0fu) == 0) {
+      printf("\n");
+    }
+  }
+
+  printf("\n\nInflate:\n\n");
+
+  /* We pass 1 to final parameter, because buf contains whole deflated
+     header data. */
+  rv = inflate_header_block(inflater, buf, outlen, 1);
+
+  if (rv != 0) {
+    free(buf);
+
+    exit(EXIT_FAILURE);
+  }
+
+  printf("\n-----------------------------------------------------------"
+         "--------------------\n");
+
+  free(buf);
+}
+
+int inflate_header_block(nghttp2_hd_inflater *inflater, uint8_t *in,
+                         size_t inlen, int final) {
+  ssize_t rv;
+
+  for (;;) {
+    nghttp2_nv nv;
+    int inflate_flags = 0;
+    size_t proclen;
+
+    rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, in, inlen, final);
+
+    if (rv < 0) {
+      fprintf(stderr, "inflate failed with error code %zd", rv);
+      return -1;
+    }
+
+    proclen = rv;
+
+    in += proclen;
+    inlen -= proclen;
+
+    if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+      fwrite(nv.name, nv.namelen, 1, stderr);
+      fprintf(stderr, ": ");
+      fwrite(nv.value, nv.valuelen, 1, stderr);
+      fprintf(stderr, "\n");
+    }
+
+    if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+      nghttp2_hd_inflate_end_headers(inflater);
+      break;
+    }
+
+    if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) {
+      break;
+    }
+  }
+
+  return 0;
+}
diff --git a/examples/libevent-client.c b/examples/libevent-client.c
new file mode 100644 (file)
index 0000000..98c194a
--- /dev/null
@@ -0,0 +1,557 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* !HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <err.h>
+#include <signal.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#include <event.h>
+#include <event2/event.h>
+#include <event2/bufferevent_ssl.h>
+#include <event2/dns.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http-parser/http_parser.h"
+
+#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
+
+typedef struct {
+  /* The NULL-terminated URI string to retreive. */
+  const char *uri;
+  /* Parsed result of the |uri| */
+  struct http_parser_url *u;
+  /* The authroity portion of the |uri|, not NULL-terminated */
+  char *authority;
+  /* The path portion of the |uri|, including query, not
+     NULL-terminated */
+  char *path;
+  /* The length of the |authority| */
+  size_t authoritylen;
+  /* The length of the |path| */
+  size_t pathlen;
+  /* The stream ID of this stream */
+  int32_t stream_id;
+} http2_stream_data;
+
+typedef struct {
+  nghttp2_session *session;
+  struct evdns_base *dnsbase;
+  struct bufferevent *bev;
+  http2_stream_data *stream_data;
+} http2_session_data;
+
+static http2_stream_data *create_http2_stream_data(const char *uri,
+                                                   struct http_parser_url *u) {
+  /* MAX 5 digits (max 65535) + 1 ':' + 1 NULL (because of snprintf) */
+  size_t extra = 7;
+  http2_stream_data *stream_data = malloc(sizeof(http2_stream_data));
+
+  stream_data->uri = uri;
+  stream_data->u = u;
+  stream_data->stream_id = -1;
+
+  stream_data->authoritylen = u->field_data[UF_HOST].len;
+  stream_data->authority = malloc(stream_data->authoritylen + extra);
+  memcpy(stream_data->authority, &uri[u->field_data[UF_HOST].off],
+         u->field_data[UF_HOST].len);
+  if (u->field_set & (1 << UF_PORT)) {
+    stream_data->authoritylen +=
+        snprintf(stream_data->authority + u->field_data[UF_HOST].len, extra,
+                 ":%u", u->port);
+  }
+
+  stream_data->pathlen = 0;
+  if (u->field_set & (1 << UF_PATH)) {
+    stream_data->pathlen = u->field_data[UF_PATH].len;
+  }
+  if (u->field_set & (1 << UF_QUERY)) {
+    /* +1 for '?' character */
+    stream_data->pathlen += u->field_data[UF_QUERY].len + 1;
+  }
+  if (stream_data->pathlen > 0) {
+    stream_data->path = malloc(stream_data->pathlen);
+    if (u->field_set & (1 << UF_PATH)) {
+      memcpy(stream_data->path, &uri[u->field_data[UF_PATH].off],
+             u->field_data[UF_PATH].len);
+    }
+    if (u->field_set & (1 << UF_QUERY)) {
+      memcpy(stream_data->path + u->field_data[UF_PATH].len + 1,
+             &uri[u->field_data[UF_QUERY].off], u->field_data[UF_QUERY].len);
+    }
+  } else {
+    stream_data->path = NULL;
+  }
+  return stream_data;
+}
+
+static void delete_http2_stream_data(http2_stream_data *stream_data) {
+  free(stream_data->path);
+  free(stream_data->authority);
+  free(stream_data);
+}
+
+/* Initializes |session_data| */
+static http2_session_data *
+create_http2_session_data(struct event_base *evbase) {
+  http2_session_data *session_data = malloc(sizeof(http2_session_data));
+
+  memset(session_data, 0, sizeof(http2_session_data));
+  session_data->dnsbase = evdns_base_new(evbase, 1);
+  return session_data;
+}
+
+static void delete_http2_session_data(http2_session_data *session_data) {
+  SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
+
+  if (ssl) {
+    SSL_shutdown(ssl);
+  }
+  bufferevent_free(session_data->bev);
+  session_data->bev = NULL;
+  evdns_base_free(session_data->dnsbase, 1);
+  session_data->dnsbase = NULL;
+  nghttp2_session_del(session_data->session);
+  session_data->session = NULL;
+  if (session_data->stream_data) {
+    delete_http2_stream_data(session_data->stream_data);
+    session_data->stream_data = NULL;
+  }
+  free(session_data);
+}
+
+static void print_header(FILE *f, const uint8_t *name, size_t namelen,
+                         const uint8_t *value, size_t valuelen) {
+  fwrite(name, namelen, 1, f);
+  fprintf(f, ": ");
+  fwrite(value, valuelen, 1, f);
+  fprintf(f, "\n");
+}
+
+/* Print HTTP headers to |f|. Please note that this function does not
+   take into account that header name and value are sequence of
+   octets, therefore they may contain non-printable characters. */
+static void print_headers(FILE *f, nghttp2_nv *nva, size_t nvlen) {
+  size_t i;
+  for (i = 0; i < nvlen; ++i) {
+    print_header(f, nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
+  }
+  fprintf(f, "\n");
+}
+
+/* nghttp2_send_callback. Here we transmit the |data|, |length| bytes,
+   to the network. Because we are using libevent bufferevent, we just
+   write those bytes into bufferevent buffer. */
+static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
+                             size_t length, int flags _U_, void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  struct bufferevent *bev = session_data->bev;
+  bufferevent_write(bev, data, length);
+  return length;
+}
+
+/* nghttp2_on_header_callback: Called when nghttp2 library emits
+   single header name/value pair. */
+static int on_header_callback(nghttp2_session *session _U_,
+                              const nghttp2_frame *frame, const uint8_t *name,
+                              size_t namelen, const uint8_t *value,
+                              size_t valuelen, uint8_t flags _U_,
+                              void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+        session_data->stream_data->stream_id == frame->hd.stream_id) {
+      /* Print response headers for the initiated request. */
+      print_header(stderr, name, namelen, value, valuelen);
+      break;
+    }
+  }
+  return 0;
+}
+
+/* nghttp2_on_begin_headers_callback: Called when nghttp2 library gets
+   started to receive header block. */
+static int on_begin_headers_callback(nghttp2_session *session _U_,
+                                     const nghttp2_frame *frame,
+                                     void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+        session_data->stream_data->stream_id == frame->hd.stream_id) {
+      fprintf(stderr, "Response headers for stream ID=%d:\n",
+              frame->hd.stream_id);
+    }
+    break;
+  }
+  return 0;
+}
+
+/* nghttp2_on_frame_recv_callback: Called when nghttp2 library
+   received a complete frame from the remote peer. */
+static int on_frame_recv_callback(nghttp2_session *session _U_,
+                                  const nghttp2_frame *frame, void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+        session_data->stream_data->stream_id == frame->hd.stream_id) {
+      fprintf(stderr, "All headers received\n");
+    }
+    break;
+  }
+  return 0;
+}
+
+/* nghttp2_on_data_chunk_recv_callback: Called when DATA frame is
+   received from the remote peer. In this implementation, if the frame
+   is meant to the stream we initiated, print the received data in
+   stdout, so that the user can redirect its output to the file
+   easily. */
+static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
+                                       uint8_t flags _U_, int32_t stream_id,
+                                       const uint8_t *data, size_t len,
+                                       void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  if (session_data->stream_data->stream_id == stream_id) {
+    fwrite(data, len, 1, stdout);
+  }
+  return 0;
+}
+
+/* nghttp2_on_stream_close_callback: Called when a stream is about to
+   closed. This example program only deals with 1 HTTP request (1
+   stream), if it is closed, we send GOAWAY and tear down the
+   session */
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                                    uint32_t error_code,
+                                    void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  int rv;
+
+  if (session_data->stream_data->stream_id == stream_id) {
+    fprintf(stderr, "Stream %d closed with error_code=%d\n", stream_id,
+            error_code);
+    rv = nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+/* NPN TLS extension client callback. We check that server advertised
+   the HTTP/2 protocol the nghttp2 library supports. If not, exit
+   the program. */
+static int select_next_proto_cb(SSL *ssl _U_, unsigned char **out,
+                                unsigned char *outlen, const unsigned char *in,
+                                unsigned int inlen, void *arg _U_) {
+  if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
+    errx(1, "Server did not advertise " NGHTTP2_PROTO_VERSION_ID);
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ssl_ctx(void) {
+  SSL_CTX *ssl_ctx;
+  ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+  if (!ssl_ctx) {
+    errx(1, "Could not create SSL/TLS context: %s",
+         ERR_error_string(ERR_get_error(), NULL));
+  }
+  SSL_CTX_set_options(ssl_ctx,
+                      SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                          SSL_OP_NO_COMPRESSION |
+                          SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+  SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, NULL);
+  return ssl_ctx;
+}
+
+/* Create SSL object */
+static SSL *create_ssl(SSL_CTX *ssl_ctx) {
+  SSL *ssl;
+  ssl = SSL_new(ssl_ctx);
+  if (!ssl) {
+    errx(1, "Could not create SSL/TLS session object: %s",
+         ERR_error_string(ERR_get_error(), NULL));
+  }
+  return ssl;
+}
+
+static void initialize_nghttp2_session(http2_session_data *session_data) {
+  nghttp2_session_callbacks *callbacks;
+
+  nghttp2_session_callbacks_new(&callbacks);
+
+  nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+
+  nghttp2_session_client_new(&session_data->session, callbacks, session_data);
+
+  nghttp2_session_callbacks_del(callbacks);
+}
+
+static void send_client_connection_header(http2_session_data *session_data) {
+  nghttp2_settings_entry iv[1] = {
+      {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+  int rv;
+
+  bufferevent_write(session_data->bev, NGHTTP2_CLIENT_CONNECTION_PREFACE,
+                    NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+  rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
+                               ARRLEN(iv));
+  if (rv != 0) {
+    errx(1, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
+  }
+}
+
+#define MAKE_NV(NAME, VALUE, VALUELEN)                                         \
+  {                                                                            \
+    (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, VALUELEN,            \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+#define MAKE_NV2(NAME, VALUE)                                                  \
+  {                                                                            \
+    (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1,   \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+/* Send HTTP request to the remote peer */
+static void submit_request(http2_session_data *session_data) {
+  int32_t stream_id;
+  http2_stream_data *stream_data = session_data->stream_data;
+  const char *uri = stream_data->uri;
+  const struct http_parser_url *u = stream_data->u;
+  nghttp2_nv hdrs[] = {
+      MAKE_NV2(":method", "GET"),
+      MAKE_NV(":scheme", &uri[u->field_data[UF_SCHEMA].off],
+              u->field_data[UF_SCHEMA].len),
+      MAKE_NV(":authority", stream_data->authority, stream_data->authoritylen),
+      MAKE_NV(":path", stream_data->path, stream_data->pathlen)};
+  fprintf(stderr, "Request headers:\n");
+  print_headers(stderr, hdrs, ARRLEN(hdrs));
+  stream_id = nghttp2_submit_request(session_data->session, NULL, hdrs,
+                                     ARRLEN(hdrs), NULL, stream_data);
+  if (stream_id < 0) {
+    errx(1, "Could not submit HTTP request: %s", nghttp2_strerror(stream_id));
+  }
+
+  stream_data->stream_id = stream_id;
+}
+
+/* Serialize the frame and send (or buffer) the data to
+   bufferevent. */
+static int session_send(http2_session_data *session_data) {
+  int rv;
+
+  rv = nghttp2_session_send(session_data->session);
+  if (rv != 0) {
+    warnx("Fatal error: %s", nghttp2_strerror(rv));
+    return -1;
+  }
+  return 0;
+}
+
+/* readcb for bufferevent. Here we get the data from the input buffer
+   of bufferevent and feed them to nghttp2 library. This may invoke
+   nghttp2 callbacks. It may also queues the frame in nghttp2 session
+   context. To send them, we call session_send() in the end. */
+static void readcb(struct bufferevent *bev, void *ptr) {
+  http2_session_data *session_data = (http2_session_data *)ptr;
+  ssize_t readlen;
+  struct evbuffer *input = bufferevent_get_input(bev);
+  size_t datalen = evbuffer_get_length(input);
+  unsigned char *data = evbuffer_pullup(input, -1);
+
+  readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
+  if (readlen < 0) {
+    warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
+    delete_http2_session_data(session_data);
+    return;
+  }
+  if (evbuffer_drain(input, readlen) != 0) {
+    warnx("Fatal error: evbuffer_drain failed");
+    delete_http2_session_data(session_data);
+    return;
+  }
+  if (session_send(session_data) != 0) {
+    delete_http2_session_data(session_data);
+    return;
+  }
+}
+
+/* writecb for bufferevent. To greaceful shutdown after sending or
+   receiving GOAWAY, we check the some conditions on the nghttp2
+   library and output buffer of bufferevent. If it indicates we have
+   no business to this session, tear down the connection. */
+static void writecb(struct bufferevent *bev _U_, void *ptr) {
+  http2_session_data *session_data = (http2_session_data *)ptr;
+  if (nghttp2_session_want_read(session_data->session) == 0 &&
+      nghttp2_session_want_write(session_data->session) == 0 &&
+      evbuffer_get_length(bufferevent_get_output(session_data->bev)) == 0) {
+    delete_http2_session_data(session_data);
+  }
+}
+
+/* eventcb for bufferevent. For the purpose of simplicity and
+   readability of the example program, we omitted the certificate and
+   peer verification. After SSL/TLS handshake is over, initialize
+   nghttp2 library session, and send client connection header. Then
+   send HTTP request. */
+static void eventcb(struct bufferevent *bev, short events, void *ptr) {
+  http2_session_data *session_data = (http2_session_data *)ptr;
+  if (events & BEV_EVENT_CONNECTED) {
+    int fd = bufferevent_getfd(bev);
+    int val = 1;
+    fprintf(stderr, "Connected\n");
+    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+    initialize_nghttp2_session(session_data);
+    send_client_connection_header(session_data);
+    submit_request(session_data);
+    if (session_send(session_data) != 0) {
+      delete_http2_session_data(session_data);
+    }
+    return;
+  }
+  if (events & BEV_EVENT_EOF) {
+    warnx("Disconnected from the remote host");
+  } else if (events & BEV_EVENT_ERROR) {
+    warnx("Network error");
+  } else if (events & BEV_EVENT_TIMEOUT) {
+    warnx("Timeout");
+  }
+  delete_http2_session_data(session_data);
+}
+
+/* Start connecting to the remote peer |host:port| */
+static void initiate_connection(struct event_base *evbase, SSL_CTX *ssl_ctx,
+                                const char *host, uint16_t port,
+                                http2_session_data *session_data) {
+  int rv;
+  struct bufferevent *bev;
+  SSL *ssl;
+
+  ssl = create_ssl(ssl_ctx);
+  bev = bufferevent_openssl_socket_new(
+      evbase, -1, ssl, BUFFEREVENT_SSL_CONNECTING,
+      BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE);
+  bufferevent_setcb(bev, readcb, writecb, eventcb, session_data);
+  rv = bufferevent_socket_connect_hostname(bev, session_data->dnsbase,
+                                           AF_UNSPEC, host, port);
+
+  if (rv != 0) {
+    errx(1, "Could not connect to the remote host %s", host);
+  }
+  session_data->bev = bev;
+}
+
+/* Get resource denoted by the |uri|. The debug and error messages are
+   printed in stderr, while the response body is printed in stdout. */
+static void run(const char *uri) {
+  struct http_parser_url u;
+  char *host;
+  uint16_t port;
+  int rv;
+  SSL_CTX *ssl_ctx;
+  struct event_base *evbase;
+  http2_session_data *session_data;
+
+  /* Parse the |uri| and stores its components in |u| */
+  rv = http_parser_parse_url(uri, strlen(uri), 0, &u);
+  if (rv != 0) {
+    errx(1, "Could not parse URI %s", uri);
+  }
+  host = strndup(&uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
+  if (!(u.field_set & (1 << UF_PORT))) {
+    port = 443;
+  } else {
+    port = u.port;
+  }
+
+  ssl_ctx = create_ssl_ctx();
+
+  evbase = event_base_new();
+
+  session_data = create_http2_session_data(evbase);
+  session_data->stream_data = create_http2_stream_data(uri, &u);
+
+  initiate_connection(evbase, ssl_ctx, host, port, session_data);
+  free(host);
+  host = NULL;
+
+  event_base_loop(evbase, 0);
+
+  event_base_free(evbase);
+  SSL_CTX_free(ssl_ctx);
+}
+
+int main(int argc, char **argv) {
+  struct sigaction act;
+
+  if (argc < 2) {
+    fprintf(stderr, "Usage: libevent-client HTTPS_URI\n");
+    exit(EXIT_FAILURE);
+  }
+
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, NULL);
+
+  OPENSSL_config(NULL);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  run(argv[1]);
+  return 0;
+}
diff --git a/examples/libevent-server.c b/examples/libevent-server.c
new file mode 100644 (file)
index 0000000..9746f23
--- /dev/null
@@ -0,0 +1,733 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* !HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <err.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#include <event.h>
+#include <event2/event.h>
+#include <event2/bufferevent_ssl.h>
+#include <event2/listener.h>
+
+#include <nghttp2/nghttp2.h>
+
+#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16)
+
+#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
+
+#define MAKE_NV(NAME, VALUE)                                                   \
+  {                                                                            \
+    (uint8_t *) NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1,   \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+struct app_context;
+typedef struct app_context app_context;
+
+typedef struct http2_stream_data {
+  struct http2_stream_data *prev, *next;
+  char *request_path;
+  int32_t stream_id;
+  int fd;
+} http2_stream_data;
+
+typedef struct http2_session_data {
+  struct http2_stream_data root;
+  struct bufferevent *bev;
+  app_context *app_ctx;
+  nghttp2_session *session;
+  char *client_addr;
+} http2_session_data;
+
+struct app_context {
+  SSL_CTX *ssl_ctx;
+  struct event_base *evbase;
+};
+
+static unsigned char next_proto_list[256];
+static size_t next_proto_list_len;
+
+static int next_proto_cb(SSL *s _U_, const unsigned char **data,
+                         unsigned int *len, void *arg _U_) {
+  *data = next_proto_list;
+  *len = (unsigned int)next_proto_list_len;
+  return SSL_TLSEXT_ERR_OK;
+}
+
+/* Create SSL_CTX. */
+static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) {
+  SSL_CTX *ssl_ctx;
+  EC_KEY *ecdh;
+
+  ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+  if (!ssl_ctx) {
+    errx(1, "Could not create SSL/TLS context: %s",
+         ERR_error_string(ERR_get_error(), NULL));
+  }
+  SSL_CTX_set_options(ssl_ctx,
+                      SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                          SSL_OP_NO_COMPRESSION |
+                          SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+
+  ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+  if (!ecdh) {
+    errx(1, "EC_KEY_new_by_curv_name failed: %s",
+         ERR_error_string(ERR_get_error(), NULL));
+  }
+  SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+  EC_KEY_free(ecdh);
+
+  if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) {
+    errx(1, "Could not read private key file %s", key_file);
+  }
+  if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
+    errx(1, "Could not read certificate file %s", cert_file);
+  }
+
+  next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN;
+  memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID,
+         NGHTTP2_PROTO_VERSION_ID_LEN);
+  next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN;
+
+  SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, NULL);
+  return ssl_ctx;
+}
+
+/* Create SSL object */
+static SSL *create_ssl(SSL_CTX *ssl_ctx) {
+  SSL *ssl;
+  ssl = SSL_new(ssl_ctx);
+  if (!ssl) {
+    errx(1, "Could not create SSL/TLS session object: %s",
+         ERR_error_string(ERR_get_error(), NULL));
+  }
+  return ssl;
+}
+
+static void add_stream(http2_session_data *session_data,
+                       http2_stream_data *stream_data) {
+  stream_data->next = session_data->root.next;
+  session_data->root.next = stream_data;
+  stream_data->prev = &session_data->root;
+  if (stream_data->next) {
+    stream_data->next->prev = stream_data;
+  }
+}
+
+static void remove_stream(http2_session_data *session_data _U_,
+                          http2_stream_data *stream_data) {
+  stream_data->prev->next = stream_data->next;
+  if (stream_data->next) {
+    stream_data->next->prev = stream_data->prev;
+  }
+}
+
+static http2_stream_data *
+create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) {
+  http2_stream_data *stream_data;
+  stream_data = malloc(sizeof(http2_stream_data));
+  memset(stream_data, 0, sizeof(http2_stream_data));
+  stream_data->stream_id = stream_id;
+  stream_data->fd = -1;
+
+  add_stream(session_data, stream_data);
+  return stream_data;
+}
+
+static void delete_http2_stream_data(http2_stream_data *stream_data) {
+  if (stream_data->fd != -1) {
+    close(stream_data->fd);
+  }
+  free(stream_data->request_path);
+  free(stream_data);
+}
+
+static http2_session_data *create_http2_session_data(app_context *app_ctx,
+                                                     int fd,
+                                                     struct sockaddr *addr,
+                                                     int addrlen) {
+  int rv;
+  http2_session_data *session_data;
+  SSL *ssl;
+  char host[NI_MAXHOST];
+  int val = 1;
+
+  ssl = create_ssl(app_ctx->ssl_ctx);
+  session_data = malloc(sizeof(http2_session_data));
+  memset(session_data, 0, sizeof(http2_session_data));
+  session_data->app_ctx = app_ctx;
+  setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+  session_data->bev = bufferevent_openssl_socket_new(
+      app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING,
+      BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
+  rv = getnameinfo(addr, addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
+  if (rv != 0) {
+    session_data->client_addr = strdup("(unknown)");
+  } else {
+    session_data->client_addr = strdup(host);
+  }
+
+  return session_data;
+}
+
+static void delete_http2_session_data(http2_session_data *session_data) {
+  http2_stream_data *stream_data;
+  SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev);
+  fprintf(stderr, "%s disconnected\n", session_data->client_addr);
+  if (ssl) {
+    SSL_shutdown(ssl);
+  }
+  bufferevent_free(session_data->bev);
+  nghttp2_session_del(session_data->session);
+  for (stream_data = session_data->root.next; stream_data;) {
+    http2_stream_data *next = stream_data->next;
+    delete_http2_stream_data(stream_data);
+    stream_data = next;
+  }
+  free(session_data->client_addr);
+  free(session_data);
+}
+
+/* Serialize the frame and send (or buffer) the data to
+   bufferevent. */
+static int session_send(http2_session_data *session_data) {
+  int rv;
+  rv = nghttp2_session_send(session_data->session);
+  if (rv != 0) {
+    warnx("Fatal error: %s", nghttp2_strerror(rv));
+    return -1;
+  }
+  return 0;
+}
+
+/* Read the data in the bufferevent and feed them into nghttp2 library
+   function. Invocation of nghttp2_session_mem_recv() may make
+   additional pending frames, so call session_send() at the end of the
+   function. */
+static int session_recv(http2_session_data *session_data) {
+  ssize_t readlen;
+  struct evbuffer *input = bufferevent_get_input(session_data->bev);
+  size_t datalen = evbuffer_get_length(input);
+  unsigned char *data = evbuffer_pullup(input, -1);
+
+  readlen = nghttp2_session_mem_recv(session_data->session, data, datalen);
+  if (readlen < 0) {
+    warnx("Fatal error: %s", nghttp2_strerror((int)readlen));
+    return -1;
+  }
+  if (evbuffer_drain(input, readlen) != 0) {
+    warnx("Fatal error: evbuffer_drain failed");
+    return -1;
+  }
+  if (session_send(session_data) != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+static ssize_t send_callback(nghttp2_session *session _U_, const uint8_t *data,
+                             size_t length, int flags _U_, void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  struct bufferevent *bev = session_data->bev;
+  /* Avoid excessive buffering in server side. */
+  if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >=
+      OUTPUT_WOULDBLOCK_THRESHOLD) {
+    return NGHTTP2_ERR_WOULDBLOCK;
+  }
+  bufferevent_write(bev, data, length);
+  return length;
+}
+
+/* Returns nonzero if the string |s| ends with the substring |sub| */
+static int ends_with(const char *s, const char *sub) {
+  size_t slen = strlen(s);
+  size_t sublen = strlen(sub);
+  if (slen < sublen) {
+    return 0;
+  }
+  return memcmp(s + slen - sublen, sub, sublen) == 0;
+}
+
+/* Returns int value of hex string character |c| */
+static uint8_t hex_to_uint(uint8_t c) {
+  if ('0' <= c && c <= '9') {
+    return c - '0';
+  }
+  if ('A' <= c && c <= 'F') {
+    return c - 'A' + 10;
+  }
+  if ('a' <= c && c <= 'f') {
+    return c - 'a' + 10;
+  }
+  return 0;
+}
+
+/* Decodes percent-encoded byte string |value| with length |valuelen|
+   and returns the decoded byte string in allocated buffer. The return
+   value is NULL terminated. The caller must free the returned
+   string. */
+static char *percent_decode(const uint8_t *value, size_t valuelen) {
+  char *res;
+
+  res = malloc(valuelen + 1);
+  if (valuelen > 3) {
+    size_t i, j;
+    for (i = 0, j = 0; i < valuelen - 2;) {
+      if (value[i] != '%' || !isxdigit(value[i + 1]) ||
+          !isxdigit(value[i + 2])) {
+        res[j++] = value[i++];
+        continue;
+      }
+      res[j++] = (hex_to_uint(value[i + 1]) << 4) + hex_to_uint(value[i + 2]);
+      i += 3;
+    }
+    memcpy(&res[j], &value[i], 2);
+    res[j + 2] = '\0';
+  } else {
+    memcpy(res, value, valuelen);
+    res[valuelen] = '\0';
+  }
+  return res;
+}
+
+static ssize_t file_read_callback(nghttp2_session *session _U_,
+                                  int32_t stream_id _U_, uint8_t *buf,
+                                  size_t length, uint32_t *data_flags,
+                                  nghttp2_data_source *source,
+                                  void *user_data _U_) {
+  int fd = source->fd;
+  ssize_t r;
+  while ((r = read(fd, buf, length)) == -1 && errno == EINTR)
+    ;
+  if (r == -1) {
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+  if (r == 0) {
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  }
+  return r;
+}
+
+static int send_response(nghttp2_session *session, int32_t stream_id,
+                         nghttp2_nv *nva, size_t nvlen, int fd) {
+  int rv;
+  nghttp2_data_provider data_prd;
+  data_prd.source.fd = fd;
+  data_prd.read_callback = file_read_callback;
+
+  rv = nghttp2_submit_response(session, stream_id, nva, nvlen, &data_prd);
+  if (rv != 0) {
+    warnx("Fatal error: %s", nghttp2_strerror(rv));
+    return -1;
+  }
+  return 0;
+}
+
+const char ERROR_HTML[] = "<html><head><title>404</title></head>"
+                          "<body><h1>404 Not Found</h1></body></html>";
+
+static int error_reply(nghttp2_session *session,
+                       http2_stream_data *stream_data) {
+  int rv;
+  ssize_t writelen;
+  int pipefd[2];
+  nghttp2_nv hdrs[] = {MAKE_NV(":status", "404")};
+
+  rv = pipe(pipefd);
+  if (rv != 0) {
+    warn("Could not create pipe");
+    rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                   stream_data->stream_id,
+                                   NGHTTP2_INTERNAL_ERROR);
+    if (rv != 0) {
+      warnx("Fatal error: %s", nghttp2_strerror(rv));
+      return -1;
+    }
+    return 0;
+  }
+
+  writelen = write(pipefd[1], ERROR_HTML, sizeof(ERROR_HTML) - 1);
+  close(pipefd[1]);
+
+  if (writelen != sizeof(ERROR_HTML) - 1) {
+    close(pipefd[0]);
+    return -1;
+  }
+
+  stream_data->fd = pipefd[0];
+
+  if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs),
+                    pipefd[0]) != 0) {
+    close(pipefd[0]);
+    return -1;
+  }
+  return 0;
+}
+
+/* nghttp2_on_header_callback: Called when nghttp2 library emits
+   single header name/value pair. */
+static int on_header_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, const uint8_t *name,
+                              size_t namelen, const uint8_t *value,
+                              size_t valuelen, uint8_t flags _U_,
+                              void *user_data _U_) {
+  http2_stream_data *stream_data;
+  const char PATH[] = ":path";
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+      break;
+    }
+    stream_data =
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+    if (!stream_data || stream_data->request_path) {
+      break;
+    }
+    if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) {
+      size_t j;
+      for (j = 0; j < valuelen && value[j] != '?'; ++j)
+        ;
+      stream_data->request_path = percent_decode(value, j);
+    }
+    break;
+  }
+  return 0;
+}
+
+static int on_begin_headers_callback(nghttp2_session *session,
+                                     const nghttp2_frame *frame,
+                                     void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  http2_stream_data *stream_data;
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+  stream_data = create_http2_stream_data(session_data, frame->hd.stream_id);
+  nghttp2_session_set_stream_user_data(session, frame->hd.stream_id,
+                                       stream_data);
+  return 0;
+}
+
+/* Minimum check for directory traversal. Returns nonzero if it is
+   safe. */
+static int check_path(const char *path) {
+  /* We don't like '\' in url. */
+  return path[0] && path[0] == '/' && strchr(path, '\\') == NULL &&
+         strstr(path, "/../") == NULL && strstr(path, "/./") == NULL &&
+         !ends_with(path, "/..") && !ends_with(path, "/.");
+}
+
+static int on_request_recv(nghttp2_session *session,
+                           http2_session_data *session_data,
+                           http2_stream_data *stream_data) {
+  int fd;
+  nghttp2_nv hdrs[] = {MAKE_NV(":status", "200")};
+  char *rel_path;
+
+  if (!stream_data->request_path) {
+    if (error_reply(session, stream_data) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    return 0;
+  }
+  fprintf(stderr, "%s GET %s\n", session_data->client_addr,
+          stream_data->request_path);
+  if (!check_path(stream_data->request_path)) {
+    if (error_reply(session, stream_data) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    return 0;
+  }
+  for (rel_path = stream_data->request_path; *rel_path == '/'; ++rel_path)
+    ;
+  fd = open(rel_path, O_RDONLY);
+  if (fd == -1) {
+    if (error_reply(session, stream_data) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    return 0;
+  }
+  stream_data->fd = fd;
+
+  if (send_response(session, stream_data->stream_id, hdrs, ARRLEN(hdrs), fd) !=
+      0) {
+    close(fd);
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+                                  const nghttp2_frame *frame, void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  http2_stream_data *stream_data;
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA:
+  case NGHTTP2_HEADERS:
+    /* Check that the client request has finished */
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      stream_data =
+          nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+      /* For DATA and HEADERS frame, this callback may be called after
+         on_stream_close_callback. Check that stream still alive. */
+      if (!stream_data) {
+        return 0;
+      }
+      return on_request_recv(session, session_data, stream_data);
+    }
+    break;
+  default:
+    break;
+  }
+  return 0;
+}
+
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                                    uint32_t error_code _U_, void *user_data) {
+  http2_session_data *session_data = (http2_session_data *)user_data;
+  http2_stream_data *stream_data;
+
+  stream_data = nghttp2_session_get_stream_user_data(session, stream_id);
+  if (!stream_data) {
+    return 0;
+  }
+  remove_stream(session_data, stream_data);
+  delete_http2_stream_data(stream_data);
+  return 0;
+}
+
+static void initialize_nghttp2_session(http2_session_data *session_data) {
+  nghttp2_option *option;
+  nghttp2_session_callbacks *callbacks;
+
+  nghttp2_option_new(&option);
+
+  /* Tells nghttp2_session object that it handles client connection
+     preface */
+  nghttp2_option_set_recv_client_preface(option, 1);
+
+  nghttp2_session_callbacks_new(&callbacks);
+
+  nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+
+  nghttp2_session_server_new2(&session_data->session, callbacks, session_data,
+                              option);
+
+  nghttp2_session_callbacks_del(callbacks);
+  nghttp2_option_del(option);
+}
+
+/* Send HTTP/2 client connection header, which includes 24 bytes
+   magic octets and SETTINGS frame */
+static int send_server_connection_header(http2_session_data *session_data) {
+  nghttp2_settings_entry iv[1] = {
+      {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
+  int rv;
+
+  rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv,
+                               ARRLEN(iv));
+  if (rv != 0) {
+    warnx("Fatal error: %s", nghttp2_strerror(rv));
+    return -1;
+  }
+  return 0;
+}
+
+/* readcb for bufferevent after client connection header was
+   checked. */
+static void readcb(struct bufferevent *bev _U_, void *ptr) {
+  http2_session_data *session_data = (http2_session_data *)ptr;
+  if (session_recv(session_data) != 0) {
+    delete_http2_session_data(session_data);
+    return;
+  }
+}
+
+/* writecb for bufferevent. To greaceful shutdown after sending or
+   receiving GOAWAY, we check the some conditions on the nghttp2
+   library and output buffer of bufferevent. If it indicates we have
+   no business to this session, tear down the connection. If the
+   connection is not going to shutdown, we call session_send() to
+   process pending data in the output buffer. This is necessary
+   because we have a threshold on the buffer size to avoid too much
+   buffering. See send_callback(). */
+static void writecb(struct bufferevent *bev, void *ptr) {
+  http2_session_data *session_data = (http2_session_data *)ptr;
+  if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) {
+    return;
+  }
+  if (nghttp2_session_want_read(session_data->session) == 0 &&
+      nghttp2_session_want_write(session_data->session) == 0) {
+    delete_http2_session_data(session_data);
+    return;
+  }
+  if (session_send(session_data) != 0) {
+    delete_http2_session_data(session_data);
+    return;
+  }
+}
+
+/* eventcb for bufferevent */
+static void eventcb(struct bufferevent *bev _U_, short events, void *ptr) {
+  http2_session_data *session_data = (http2_session_data *)ptr;
+  if (events & BEV_EVENT_CONNECTED) {
+    fprintf(stderr, "%s connected\n", session_data->client_addr);
+
+    initialize_nghttp2_session(session_data);
+
+    if (send_server_connection_header(session_data) != 0) {
+      delete_http2_session_data(session_data);
+      return;
+    }
+
+    return;
+  }
+  if (events & BEV_EVENT_EOF) {
+    fprintf(stderr, "%s EOF\n", session_data->client_addr);
+  } else if (events & BEV_EVENT_ERROR) {
+    fprintf(stderr, "%s network error\n", session_data->client_addr);
+  } else if (events & BEV_EVENT_TIMEOUT) {
+    fprintf(stderr, "%s timeout\n", session_data->client_addr);
+  }
+  delete_http2_session_data(session_data);
+}
+
+/* callback for evconnlistener */
+static void acceptcb(struct evconnlistener *listener _U_, int fd,
+                     struct sockaddr *addr, int addrlen, void *arg) {
+  app_context *app_ctx = (app_context *)arg;
+  http2_session_data *session_data;
+
+  session_data = create_http2_session_data(app_ctx, fd, addr, addrlen);
+
+  bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data);
+}
+
+static void start_listen(struct event_base *evbase, const char *service,
+                         app_context *app_ctx) {
+  int rv;
+  struct addrinfo hints;
+  struct addrinfo *res, *rp;
+
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+  hints.ai_flags |= AI_ADDRCONFIG;
+#endif /* AI_ADDRCONFIG */
+
+  rv = getaddrinfo(NULL, service, &hints, &res);
+  if (rv != 0) {
+    errx(1, NULL);
+  }
+  for (rp = res; rp; rp = rp->ai_next) {
+    struct evconnlistener *listener;
+    listener = evconnlistener_new_bind(
+        evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
+        16, rp->ai_addr, rp->ai_addrlen);
+    if (listener) {
+      freeaddrinfo(res);
+
+      return;
+    }
+  }
+  errx(1, "Could not start listener");
+}
+
+static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx,
+                                   struct event_base *evbase) {
+  memset(app_ctx, 0, sizeof(app_context));
+  app_ctx->ssl_ctx = ssl_ctx;
+  app_ctx->evbase = evbase;
+}
+
+static void run(const char *service, const char *key_file,
+                const char *cert_file) {
+  SSL_CTX *ssl_ctx;
+  app_context app_ctx;
+  struct event_base *evbase;
+
+  ssl_ctx = create_ssl_ctx(key_file, cert_file);
+  evbase = event_base_new();
+  initialize_app_context(&app_ctx, ssl_ctx, evbase);
+  start_listen(evbase, service, &app_ctx);
+
+  event_base_loop(evbase, 0);
+
+  event_base_free(evbase);
+  SSL_CTX_free(ssl_ctx);
+}
+
+int main(int argc, char **argv) {
+  struct sigaction act;
+
+  if (argc < 4) {
+    fprintf(stderr, "Usage: libevent-server PORT KEY_FILE CERT_FILE\n");
+    exit(EXIT_FAILURE);
+  }
+
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, NULL);
+
+  OPENSSL_config(NULL);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  run(argv[1], argv[2], argv[3]);
+  return 0;
+}
diff --git a/examples/tiny-nghttpd.c b/examples/tiny-nghttpd.c
new file mode 100644 (file)
index 0000000..c46711c
--- /dev/null
@@ -0,0 +1,1301 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+/*
+ * This program is intended to measure library performance, avoiding
+ * overhead of underlying I/O library (e.g., libevent, Boost ASIO).
+ */
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* !HAVE_CONFIG_H */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+
+#include <nghttp2/nghttp2.h>
+
+#define SERVER_NAME "tiny-nghttpd nghttp2/" NGHTTP2_VERSION
+
+#define MAKE_NV(name, value)                                                   \
+  {                                                                            \
+    (uint8_t *)(name), (uint8_t *)(value), sizeof((name)) - 1,                 \
+        sizeof((value)) - 1, NGHTTP2_NV_FLAG_NONE                              \
+  }
+
+#define MAKE_NV2(name, value, valuelen)                                        \
+  {                                                                            \
+    (uint8_t *)(name), (uint8_t *)(value), sizeof((name)) - 1, (valuelen),     \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+#define array_size(a) (sizeof((a)) / sizeof((a)[0]))
+
+/* Returns the length of remaning data in buffer */
+#define io_buf_len(iobuf) ((iobuf)->last - (iobuf)->pos)
+/* Returns the space buffer can still accept */
+#define io_buf_left(iobuf) ((iobuf)->end - (iobuf)->last)
+
+typedef struct {
+  /* beginning of buffer */
+  uint8_t *begin;
+  /* one byte beyond the end of buffer */
+  uint8_t *end;
+  /* next read/write position of buffer */
+  uint8_t *pos;
+  /* one byte beyond last data of buffer */
+  uint8_t *last;
+} io_buf;
+
+typedef struct {
+  /* epoll fd */
+  int epfd;
+} io_loop;
+
+typedef struct stream {
+  struct stream *prev, *next;
+  /* mandatory header fields */
+  char *method;
+  char *scheme;
+  char *authority;
+  char *path;
+  char *host;
+  /* region of response body in rawscrbuf */
+  uint8_t *res_begin, *res_end;
+  /* io_buf wrapping rawscrbuf */
+  io_buf scrbuf;
+  int64_t fileleft;
+  /* length of mandatory header fields */
+  size_t methodlen;
+  size_t schemelen;
+  size_t authoritylen;
+  size_t pathlen;
+  size_t hostlen;
+  /* stream ID of this stream */
+  int32_t stream_id;
+  /* fd for reading file */
+  int filefd;
+  /* scratch buffer for this stream */
+  uint8_t rawscrbuf[4096];
+} stream;
+
+typedef struct { int (*handler)(io_loop *, uint32_t, void *); } evhandle;
+
+typedef struct {
+  evhandle evhn;
+  nghttp2_session *session;
+  /* list of stream */
+  stream strm_head;
+  /* pending library output */
+  const uint8_t *cache;
+  /* io_buf wrapping rawoutbuf */
+  io_buf buf;
+  /* length of cache */
+  size_t cachelen;
+  /* client fd */
+  int fd;
+  /* output buffer */
+  uint8_t rawoutbuf[65536];
+} connection;
+
+typedef struct {
+  evhandle evhn;
+  /* listening fd */
+  int fd;
+} server;
+
+typedef struct {
+  evhandle evhn;
+  /* timerfd */
+  int fd;
+} timer;
+
+/* document root */
+const char *docroot;
+/* length of docroot */
+size_t docrootlen;
+
+nghttp2_session_callbacks *shared_callbacks;
+nghttp2_option *shared_option;
+
+static int handle_accept(io_loop *loop, uint32_t events, void *ptr);
+static int handle_connection(io_loop *loop, uint32_t events, void *ptr);
+static int handle_timer(io_loop *loop, uint32_t events, void *ptr);
+
+static void io_buf_init(io_buf *buf, uint8_t *underlying, size_t len) {
+  buf->begin = buf->pos = buf->last = underlying;
+  buf->end = underlying + len;
+}
+
+static void io_buf_add(io_buf *buf, const void *src, size_t len) {
+  memcpy(buf->last, src, len);
+  buf->last += len;
+}
+
+static char *io_buf_add_str(io_buf *buf, const void *src, size_t len) {
+  uint8_t *start = buf->last;
+
+  memcpy(buf->last, src, len);
+  buf->last += len;
+  *buf->last++ = '\0';
+
+  return (char *)start;
+}
+
+static int memseq(const uint8_t *a, size_t alen, const char *b) {
+  const uint8_t *last = a + alen;
+
+  for (; a != last && *b && *a == *b; ++a, ++b)
+    ;
+
+  return a == last && *b == 0;
+}
+
+static char *cpydig(char *buf, int n, size_t len) {
+  char *p;
+
+  p = buf + len - 1;
+  do {
+    *p-- = (n % 10) + '0';
+    n /= 10;
+  } while (p >= buf);
+
+  return buf + len;
+}
+
+static const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+static const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed",
+                                    "Thu", "Fri", "Sat"};
+
+static size_t http_date(char *buf, time_t t) {
+  struct tm tms;
+  char *p = buf;
+
+  if (gmtime_r(&t, &tms) == NULL) {
+    return 0;
+  }
+
+  /* Sat, 27 Sep 2014 06:31:15 GMT */
+
+  memcpy(p, DAY_OF_WEEK[tms.tm_wday], 3);
+  p += 3;
+  *p++ = ',';
+  *p++ = ' ';
+  p = cpydig(p, tms.tm_mday, 2);
+  *p++ = ' ';
+  memcpy(p, MONTH[tms.tm_mon], 3);
+  p += 3;
+  *p++ = ' ';
+  p = cpydig(p, tms.tm_year + 1900, 4);
+  *p++ = ' ';
+  p = cpydig(p, tms.tm_hour, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_min, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_sec, 2);
+  memcpy(p, " GMT", 4);
+  p += 4;
+
+  return p - buf;
+}
+
+static char date[29];
+static char datelen;
+
+static void update_date() { datelen = http_date(date, time(NULL)); }
+
+static size_t utos(char *buf, size_t len, uint64_t n) {
+  size_t nwrite = 0;
+  uint64_t t = n;
+
+  if (len == 0) {
+    return 0;
+  }
+
+  if (n == 0) {
+    buf[0] = '0';
+    return 1;
+  }
+
+  for (; t; t /= 10, ++nwrite)
+    ;
+
+  if (nwrite > len) {
+    return 0;
+  }
+
+  buf += nwrite - 1;
+  do {
+    *buf-- = (n % 10) + '0';
+    n /= 10;
+  } while (n);
+
+  return nwrite;
+}
+
+static void print_errno(const char *prefix, int errnum) {
+  char buf[1024];
+  char *errmsg;
+
+  errmsg = strerror_r(errnum, buf, sizeof(buf));
+
+  fprintf(stderr, "%s: %s\n", prefix, errmsg);
+}
+
+#define list_insert(head, elem)                                                \
+  do {                                                                         \
+    (elem)->prev = (head);                                                     \
+    (elem)->next = (head)->next;                                               \
+                                                                               \
+    if ((head)->next) {                                                        \
+      (head)->next->prev = (elem);                                             \
+    }                                                                          \
+    (head)->next = (elem);                                                     \
+  } while (0)
+
+#define list_remove(elem)                                                      \
+  do {                                                                         \
+    (elem)->prev->next = (elem)->next;                                         \
+    if ((elem)->next) {                                                        \
+      (elem)->next->prev = (elem)->prev;                                       \
+    }                                                                          \
+  } while (0)
+
+static stream *stream_new(int32_t stream_id, connection *conn) {
+  stream *strm;
+
+  strm = malloc(sizeof(stream));
+
+  strm->prev = strm->next = NULL;
+  strm->method = NULL;
+  strm->scheme = NULL;
+  strm->authority = NULL;
+  strm->path = NULL;
+  strm->host = NULL;
+  strm->res_begin = NULL;
+  strm->res_end = NULL;
+  strm->methodlen = 0;
+  strm->schemelen = 0;
+  strm->authoritylen = 0;
+  strm->pathlen = 0;
+  strm->hostlen = 0;
+  strm->stream_id = stream_id;
+  strm->filefd = -1;
+  strm->fileleft = 0;
+
+  list_insert(&conn->strm_head, strm);
+
+  io_buf_init(&strm->scrbuf, strm->rawscrbuf, sizeof(strm->rawscrbuf));
+
+  return strm;
+}
+
+static void stream_del(stream *strm) {
+  list_remove(strm);
+
+  if (strm->filefd != -1) {
+    close(strm->filefd);
+  }
+
+  free(strm);
+}
+
+static connection *connection_new(int fd) {
+  connection *conn;
+  int rv;
+
+  conn = malloc(sizeof(connection));
+
+  rv = nghttp2_session_server_new2(&conn->session, shared_callbacks, conn,
+                                   shared_option);
+
+  if (rv != 0) {
+    goto cleanup;
+  }
+
+  conn->fd = fd;
+  conn->cache = NULL;
+  conn->cachelen = 0;
+  io_buf_init(&conn->buf, conn->rawoutbuf, sizeof(conn->rawoutbuf));
+  conn->evhn.handler = handle_connection;
+  conn->strm_head.next = NULL;
+
+  return conn;
+
+cleanup:
+  free(conn);
+  return NULL;
+}
+
+static void connection_del(connection *conn) {
+  stream *strm;
+
+  nghttp2_session_del(conn->session);
+  shutdown(conn->fd, SHUT_WR);
+  close(conn->fd);
+
+  strm = conn->strm_head.next;
+  while (strm) {
+    stream *next_strm = strm->next;
+
+    stream_del(strm);
+    strm = next_strm;
+  }
+
+  free(conn);
+}
+
+static int connection_start(connection *conn) {
+  nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
+  int rv;
+
+  rv = nghttp2_submit_settings(conn->session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int server_init(server *serv, const char *node, const char *service) {
+  int rv;
+  struct addrinfo hints;
+  struct addrinfo *res, *rp;
+  int fd;
+  int on = 1;
+  socklen_t optlen = sizeof(on);
+
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+  hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+
+  rv = getaddrinfo(node, service, &hints, &res);
+
+  if (rv != 0) {
+    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
+    return -1;
+  }
+
+  for (rp = res; rp; rp = rp->ai_next) {
+    fd =
+        socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
+
+    if (fd == -1) {
+      continue;
+    }
+
+    rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, optlen);
+
+    if (rv == -1) {
+      print_errno("setsockopt", errno);
+    }
+
+    if (bind(fd, rp->ai_addr, rp->ai_addrlen) != 0) {
+      close(fd);
+      continue;
+    }
+
+    if (listen(fd, 65536) != 0) {
+      close(fd);
+      continue;
+    }
+
+    break;
+  }
+
+  freeaddrinfo(res);
+
+  if (!rp) {
+    fprintf(stderr, "No address to bind\n");
+    return -1;
+  }
+
+  serv->fd = fd;
+  serv->evhn.handler = handle_accept;
+
+  return 0;
+}
+
+static int timer_init(timer *tmr) {
+  int fd;
+  struct itimerspec timerval = {{1, 0}, {1, 0}};
+
+  fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+  if (fd == -1) {
+    print_errno("timerfd_create", errno);
+    return -1;
+  }
+
+  if (timerfd_settime(fd, 0, &timerval, NULL) != 0) {
+    print_errno("timerfd_settime", errno);
+    return -1;
+  }
+
+  tmr->fd = fd;
+  tmr->evhn.handler = handle_timer;
+
+  return 0;
+}
+
+static int io_loop_init(io_loop *loop) {
+  int epfd;
+
+  epfd = epoll_create1(0);
+
+  if (epfd == -1) {
+    print_errno("epoll_create", errno);
+    return -1;
+  }
+
+  loop->epfd = epfd;
+
+  return 0;
+}
+
+static int io_loop_ctl(io_loop *loop, int op, int fd, uint32_t events,
+                       void *ptr) {
+  int rv;
+  struct epoll_event ev;
+
+  ev.events = events;
+  ev.data.ptr = ptr;
+
+  rv = epoll_ctl(loop->epfd, op, fd, &ev);
+
+  if (rv != 0) {
+    print_errno("epoll_ctl", errno);
+    return -1;
+  }
+
+  return 0;
+}
+
+static int io_loop_add(io_loop *loop, int fd, uint32_t events, void *ptr) {
+  return io_loop_ctl(loop, EPOLL_CTL_ADD, fd, events, ptr);
+}
+
+static int io_loop_mod(io_loop *loop, int fd, uint32_t events, void *ptr) {
+  return io_loop_ctl(loop, EPOLL_CTL_MOD, fd, events, ptr);
+}
+
+static int io_loop_run(io_loop *loop, server *serv _U_) {
+#define NUM_EVENTS 1024
+  struct epoll_event events[NUM_EVENTS];
+
+  for (;;) {
+    int nev;
+    evhandle *evhn;
+    struct epoll_event *ev, *end;
+
+    while ((nev = epoll_wait(loop->epfd, events, NUM_EVENTS, -1)) == -1 &&
+           errno == EINTR)
+      ;
+
+    if (nev == -1) {
+      print_errno("epoll_wait", errno);
+      return -1;
+    }
+
+    for (ev = events, end = events + nev; ev != end; ++ev) {
+      evhn = ev->data.ptr;
+      evhn->handler(loop, ev->events, ev->data.ptr);
+    }
+  }
+}
+
+static int handle_timer(io_loop *loop _U_, uint32_t events _U_, void *ptr) {
+  timer *tmr = ptr;
+  int64_t buf;
+  ssize_t nread;
+
+  while ((nread = read(tmr->fd, &buf, sizeof(buf))) == -1 && errno == EINTR)
+    ;
+
+  assert(nread == sizeof(buf));
+
+  update_date();
+
+  return 0;
+}
+
+static int handle_accept(io_loop *loop, uint32_t events _U_, void *ptr) {
+  int acfd;
+  server *serv = ptr;
+  int on = 1;
+  socklen_t optlen = sizeof(on);
+  int rv;
+
+  for (;;) {
+    connection *conn;
+
+    while ((acfd = accept4(serv->fd, NULL, NULL, SOCK_NONBLOCK)) == -1 &&
+           errno == EINTR)
+      ;
+
+    if (acfd == -1) {
+      switch (errno) {
+      case ENETDOWN:
+      case EPROTO:
+      case ENOPROTOOPT:
+      case EHOSTDOWN:
+      case ENONET:
+      case EHOSTUNREACH:
+      case EOPNOTSUPP:
+      case ENETUNREACH:
+        continue;
+      }
+      return 0;
+    }
+
+    rv = setsockopt(acfd, IPPROTO_TCP, TCP_NODELAY, &on, optlen);
+
+    if (rv == -1) {
+      print_errno("setsockopt", errno);
+    }
+
+    conn = connection_new(acfd);
+
+    if (conn == NULL) {
+      close(acfd);
+      continue;
+    }
+
+    if (connection_start(conn) != 0 ||
+        io_loop_add(loop, acfd, EPOLLIN | EPOLLOUT, conn) != 0) {
+      connection_del(conn);
+    }
+  }
+}
+
+static void stream_error(connection *conn, int32_t stream_id,
+                         uint32_t error_code) {
+  nghttp2_submit_rst_stream(conn->session, NGHTTP2_FLAG_NONE, stream_id,
+                            error_code);
+}
+
+static ssize_t fd_read_callback(nghttp2_session *session _U_,
+                                int32_t stream_id _U_, uint8_t *buf,
+                                size_t length, uint32_t *data_flags,
+                                nghttp2_data_source *source,
+                                void *user_data _U_) {
+  stream *strm = source->ptr;
+  ssize_t nread;
+
+  while ((nread = read(strm->filefd, buf, length)) == -1 && errno == EINTR)
+    ;
+  if (nread == -1) {
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+  strm->fileleft -= nread;
+  if (nread == 0 || strm->fileleft == 0) {
+    if (strm->fileleft != 0) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  }
+  return nread;
+}
+
+static ssize_t resbuf_read_callback(nghttp2_session *session _U_,
+                                    int32_t stream_id _U_, uint8_t *buf,
+                                    size_t length, uint32_t *data_flags,
+                                    nghttp2_data_source *source,
+                                    void *user_data _U_) {
+  stream *strm = source->ptr;
+  size_t left = strm->res_end - strm->res_begin;
+  size_t nwrite = length < left ? length : left;
+
+  memcpy(buf, strm->res_begin, nwrite);
+  strm->res_begin += nwrite;
+
+  if (strm->res_begin == strm->res_end) {
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  }
+
+  return nwrite;
+}
+
+static int hex_digit(char c) {
+  return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F') ||
+         ('a' <= c && c <= 'f');
+}
+
+static unsigned int hex_to_uint(char c) {
+  if (c <= '9') {
+    return c - '0';
+  }
+
+  if (c <= 'F') {
+    return c - 'A' + 10;
+  }
+
+  return c - 'a' + 10;
+}
+
+static void percent_decode(io_buf *buf, const char *s) {
+  for (; *s; ++s) {
+    if (*s == '?' || *s == '#') {
+      break;
+    }
+
+    if (*s == '%' && hex_digit(*(s + 1)) && hex_digit(*(s + 2))) {
+      *buf->last++ = (hex_to_uint(*(s + 1)) << 4) + hex_to_uint(*(s + 2));
+      s += 2;
+      continue;
+    }
+
+    *buf->last++ = *s;
+  }
+}
+
+static int check_path(const char *path, size_t len) {
+  return path[0] == '/' && strchr(path, '\\') == NULL &&
+         strstr(path, "/../") == NULL && strstr(path, "/./") == NULL &&
+         (len < 3 || memcmp(path + len - 3, "/..", 3) != 0) &&
+         (len < 2 || memcmp(path + len - 2, "/.", 2) != 0);
+}
+
+static int make_path(io_buf *pathbuf, const char *req, size_t reqlen _U_) {
+  uint8_t *p;
+
+  if (req[0] != '/') {
+    return -1;
+  }
+
+  if (docrootlen + strlen(req) + sizeof("index.html") >
+      (size_t)io_buf_left(pathbuf)) {
+    return -1;
+  }
+
+  io_buf_add(pathbuf, docroot, docrootlen);
+
+  p = pathbuf->last;
+
+  percent_decode(pathbuf, req);
+
+  if (*(pathbuf->last - 1) == '/') {
+    io_buf_add(pathbuf, "index.html", sizeof("index.html") - 1);
+  }
+
+  *pathbuf->last++ = '\0';
+
+  if (!check_path((const char *)p, pathbuf->last - 1 - p)) {
+
+    return -1;
+  }
+
+  return 0;
+}
+
+static int status_response(stream *strm, connection *conn,
+                           const char *status_code) {
+  int rv;
+  size_t status_codelen = strlen(status_code);
+  char contentlength[19];
+  size_t contentlengthlen;
+  size_t reslen;
+  nghttp2_data_provider prd, *prdptr;
+  nghttp2_nv nva[5] = {
+      MAKE_NV(":status", ""), MAKE_NV("server", SERVER_NAME),
+      MAKE_NV2("date", date, datelen), MAKE_NV("content-length", ""),
+  };
+  size_t nvlen = 3;
+
+  nva[0].value = (uint8_t *)status_code;
+  nva[0].valuelen = strlen(status_code);
+
+#define BODY1 "<html><head><title>"
+#define BODY2 "</title></head><body><h1>"
+#define BODY3 "</h1></body></html>"
+
+  reslen = sizeof(BODY1) - 1 + sizeof(BODY2) - 1 + sizeof(BODY3) - 1 +
+           status_codelen * 2;
+
+  if ((size_t)io_buf_left(&strm->scrbuf) < reslen) {
+    contentlength[0] = '0';
+    contentlengthlen = 1;
+    prdptr = NULL;
+  } else {
+    contentlengthlen = utos(contentlength, sizeof(contentlength), reslen);
+
+    strm->res_begin = strm->scrbuf.last;
+
+    io_buf_add(&strm->scrbuf, BODY1, sizeof(BODY1) - 1);
+    io_buf_add(&strm->scrbuf, status_code, strlen(status_code));
+    io_buf_add(&strm->scrbuf, BODY2, sizeof(BODY2) - 1);
+    io_buf_add(&strm->scrbuf, status_code, strlen(status_code));
+    io_buf_add(&strm->scrbuf, BODY3, sizeof(BODY3) - 1);
+
+    strm->res_end = strm->scrbuf.last;
+    prdptr = &prd;
+  }
+
+  nva[nvlen].value = (uint8_t *)contentlength;
+  nva[nvlen].valuelen = contentlengthlen;
+
+  ++nvlen;
+
+  prd.source.ptr = strm;
+  prd.read_callback = resbuf_read_callback;
+
+  rv = nghttp2_submit_response(conn->session, strm->stream_id, nva, nvlen,
+                               prdptr);
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int redirect_response(stream *strm, connection *conn) {
+  int rv;
+  size_t locationlen;
+  nghttp2_nv nva[5] = {
+      MAKE_NV(":status", "301"),       MAKE_NV("server", SERVER_NAME),
+      MAKE_NV2("date", date, datelen), MAKE_NV("content-length", "0"),
+      MAKE_NV("location", ""),
+  };
+
+  /* + 1 for trailing '/' */
+  locationlen = strm->schemelen + 3 + strm->hostlen + strm->pathlen + 1;
+  if ((size_t)io_buf_left(&strm->scrbuf) < locationlen) {
+    return -1;
+  }
+
+  nva[4].value = strm->scrbuf.last;
+  nva[4].valuelen = locationlen;
+
+  io_buf_add(&strm->scrbuf, strm->scheme, strm->schemelen);
+  io_buf_add(&strm->scrbuf, "://", 3);
+  io_buf_add(&strm->scrbuf, strm->host, strm->hostlen);
+  io_buf_add(&strm->scrbuf, strm->path, strm->pathlen);
+  *strm->scrbuf.last++ = '/';
+
+  rv = nghttp2_submit_response(conn->session, strm->stream_id, nva,
+                               array_size(nva), NULL);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int process_request(stream *strm, connection *conn) {
+  int fd;
+  struct stat stbuf;
+  int rv;
+  nghttp2_data_provider prd;
+  char lastmod[32];
+  size_t lastmodlen;
+  char contentlength[19];
+  size_t contentlengthlen;
+  char path[1024];
+  io_buf pathbuf;
+  nghttp2_nv nva[5] = {
+      MAKE_NV(":status", "200"), MAKE_NV("server", SERVER_NAME),
+      MAKE_NV2("date", date, datelen), MAKE_NV("content-length", ""),
+  };
+  size_t nvlen = 3;
+
+  io_buf_init(&pathbuf, (uint8_t *)path, sizeof(path));
+
+  rv = make_path(&pathbuf, strm->path, strm->pathlen);
+
+  if (rv != 0) {
+    return status_response(strm, conn, "400");
+  }
+
+  fd = open(path, O_RDONLY);
+
+  if (fd == -1) {
+    return status_response(strm, conn, "404");
+  }
+
+  strm->filefd = fd;
+
+  rv = fstat(fd, &stbuf);
+
+  if (rv == -1) {
+    return status_response(strm, conn, "404");
+  }
+
+  if (stbuf.st_mode & S_IFDIR) {
+    return redirect_response(strm, conn);
+  }
+
+  prd.source.ptr = strm;
+  prd.read_callback = fd_read_callback;
+
+  strm->fileleft = stbuf.st_size;
+
+  lastmodlen = http_date(lastmod, stbuf.st_mtim.tv_sec);
+  contentlengthlen = utos(contentlength, sizeof(contentlength), stbuf.st_size);
+
+  nva[nvlen].value = (uint8_t *)contentlength;
+  nva[nvlen].valuelen = contentlengthlen;
+
+  ++nvlen;
+
+  if (lastmodlen) {
+    nva[nvlen].name = (uint8_t *)"last-modified";
+    nva[nvlen].namelen = sizeof("last-modified") - 1;
+    nva[nvlen].value = (uint8_t *)lastmod;
+    nva[nvlen].valuelen = lastmodlen;
+    nva[nvlen].flags = NGHTTP2_NV_FLAG_NONE;
+
+    ++nvlen;
+  }
+
+  rv =
+      nghttp2_submit_response(conn->session, strm->stream_id, nva, nvlen, &prd);
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+static int on_begin_headers_callback(nghttp2_session *session,
+                                     const nghttp2_frame *frame,
+                                     void *user_data) {
+  connection *conn = user_data;
+  stream *strm;
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  strm = stream_new(frame->hd.stream_id, conn);
+
+  if (!strm) {
+    nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
+                              NGHTTP2_INTERNAL_ERROR);
+    return 0;
+  }
+
+  nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, strm);
+
+  return 0;
+}
+
+static int on_header_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, const uint8_t *name,
+                              size_t namelen, const uint8_t *value,
+                              size_t valuelen, uint8_t flags _U_,
+                              void *user_data) {
+  connection *conn = user_data;
+  stream *strm;
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  strm = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+
+  if (!strm) {
+    return 0;
+  }
+
+  if (!nghttp2_check_header_name(name, namelen) ||
+      !nghttp2_check_header_value(value, valuelen)) {
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  if (memseq(name, namelen, ":method")) {
+    if (strm->method) {
+      stream_error(conn, strm->stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->method = io_buf_add_str(&strm->scrbuf, value, valuelen);
+    if (!strm->method) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->methodlen = valuelen;
+    return 0;
+  }
+
+  if (memseq(name, namelen, ":scheme")) {
+    if (strm->scheme) {
+      stream_error(conn, strm->stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->scheme = io_buf_add_str(&strm->scrbuf, value, valuelen);
+    if (!strm->scheme) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->schemelen = valuelen;
+    return 0;
+  }
+
+  if (memseq(name, namelen, ":authority")) {
+    if (strm->authority) {
+      stream_error(conn, strm->stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->authority = io_buf_add_str(&strm->scrbuf, value, valuelen);
+    if (!strm->authority) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->authoritylen = valuelen;
+    return 0;
+  }
+
+  if (memseq(name, namelen, ":path")) {
+    if (strm->path) {
+      stream_error(conn, strm->stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->path = io_buf_add_str(&strm->scrbuf, value, valuelen);
+    if (!strm->path) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->pathlen = valuelen;
+    return 0;
+  }
+
+  if (name[0] == ':') {
+    stream_error(conn, strm->stream_id, NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  if (memseq(name, namelen, "host") && !strm->host) {
+    strm->host = io_buf_add_str(&strm->scrbuf, value, valuelen);
+    if (!strm->host) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    strm->hostlen = valuelen;
+  }
+
+  return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session,
+                                  const nghttp2_frame *frame, void *user_data) {
+  connection *conn = user_data;
+  stream *strm;
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  strm = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
+
+  if (!strm) {
+    return 0;
+  }
+
+  if (!strm->method || !strm->scheme || !strm->path ||
+      (!strm->authority && !strm->host)) {
+    stream_error(conn, strm->stream_id, NGHTTP2_PROTOCOL_ERROR);
+    return 0;
+  }
+
+  if (!strm->host) {
+    strm->host = strm->authority;
+    strm->hostlen = strm->authoritylen;
+  }
+
+  if (process_request(strm, conn) != 0) {
+    stream_error(conn, strm->stream_id, NGHTTP2_INTERNAL_ERROR);
+    return 0;
+  }
+
+  return 0;
+}
+
+static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                                    uint32_t error_code _U_,
+                                    void *user_data _U_) {
+  stream *strm;
+
+  strm = nghttp2_session_get_stream_user_data(session, stream_id);
+
+  if (!strm) {
+    return 0;
+  }
+
+  stream_del(strm);
+
+  return 0;
+}
+
+static int on_frame_not_send_callback(nghttp2_session *session _U_,
+                                      const nghttp2_frame *frame,
+                                      int lib_error_code _U_, void *user_data) {
+  connection *conn = user_data;
+
+  if (frame->hd.type != NGHTTP2_HEADERS) {
+    return 0;
+  }
+
+  /* Issue RST_STREAM so that stream does not hang around. */
+  nghttp2_submit_rst_stream(conn->session, NGHTTP2_FLAG_NONE,
+                            frame->hd.stream_id, NGHTTP2_INTERNAL_ERROR);
+
+  return 0;
+}
+
+static int do_read(connection *conn) {
+  uint8_t buf[32768];
+
+  for (;;) {
+    ssize_t nread;
+    ssize_t nproc;
+
+    while ((nread = read(conn->fd, buf, sizeof(buf))) == -1 && errno == EINTR)
+      ;
+    if (nread == -1) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        return 0;
+      }
+
+      return -1;
+    }
+
+    if (nread == 0) {
+      return -1;
+    }
+
+    nproc = nghttp2_session_mem_recv(conn->session, buf, nread);
+
+    if (nproc < 0) {
+      return -1;
+    }
+  }
+}
+
+static int do_write(connection *conn) {
+  for (;;) {
+    if (io_buf_len(&conn->buf)) {
+      ssize_t nwrite;
+      while ((nwrite = write(conn->fd, conn->buf.pos,
+                             io_buf_len(&conn->buf))) == -1 &&
+             errno == EINTR)
+        ;
+      if (nwrite == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          return 0;
+        }
+        return -1;
+      }
+
+      conn->buf.pos += nwrite;
+
+      if (io_buf_len(&conn->buf)) {
+        return 0;
+      }
+
+      io_buf_init(&conn->buf, conn->rawoutbuf, sizeof(conn->rawoutbuf));
+    }
+
+    if (conn->cache) {
+      io_buf_add(&conn->buf, conn->cache, conn->cachelen);
+      conn->cache = NULL;
+      conn->cachelen = 0;
+    }
+
+    for (;;) {
+      ssize_t n;
+      const uint8_t *b;
+
+      n = nghttp2_session_mem_send(conn->session, &b);
+
+      if (n < 0) {
+        return -1;
+      }
+
+      if (n == 0) {
+        if (io_buf_len(&conn->buf) == 0) {
+          return 0;
+        }
+        break;
+      }
+
+      if (io_buf_left(&conn->buf) < n) {
+        conn->cache = b;
+        conn->cachelen = n;
+        break;
+      }
+
+      io_buf_add(&conn->buf, b, n);
+    }
+  }
+}
+
+static int handle_connection(io_loop *loop, uint32_t events, void *ptr) {
+  connection *conn = ptr;
+  int rv;
+  uint32_t nextev = 0;
+
+  if (events & (EPOLLHUP | EPOLLERR)) {
+    goto cleanup;
+  }
+
+  if (events & EPOLLIN) {
+    rv = do_read(conn);
+
+    if (rv != 0) {
+      goto cleanup;
+    }
+  }
+
+  rv = do_write(conn);
+
+  if (rv != 0) {
+    goto cleanup;
+  }
+
+  if (nghttp2_session_want_read(conn->session)) {
+    nextev |= EPOLLIN;
+  }
+
+  if (io_buf_len(&conn->buf) || nghttp2_session_want_write(conn->session)) {
+    nextev |= EPOLLOUT;
+  }
+
+  if (!nextev) {
+    goto cleanup;
+  }
+
+  io_loop_mod(loop, conn->fd, nextev, conn);
+
+  return 0;
+
+cleanup:
+  connection_del(conn);
+
+  return 0;
+}
+
+int main(int argc, char **argv) {
+  int rv;
+  server serv;
+  timer tmr;
+  io_loop loop;
+  struct sigaction act;
+  const char *address;
+  const char *service;
+
+  if (argc < 4) {
+    fprintf(stderr, "Usage: tiny-nghttpd <address> <port> <doc-root>\n");
+    exit(EXIT_FAILURE);
+  }
+
+  address = argv[1];
+  service = argv[2];
+  docroot = argv[3];
+  docrootlen = strlen(docroot);
+
+  memset(&act, 0, sizeof(act));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, NULL);
+
+  rv = server_init(&serv, address, service);
+
+  if (rv != 0) {
+    exit(EXIT_FAILURE);
+  }
+
+  rv = timer_init(&tmr);
+
+  if (rv != 0) {
+    exit(EXIT_FAILURE);
+  }
+
+  rv = io_loop_init(&loop);
+
+  if (rv != 0) {
+    exit(EXIT_FAILURE);
+  }
+
+  rv = nghttp2_session_callbacks_new(&shared_callbacks);
+  if (rv != 0) {
+    fprintf(stderr, "nghttp2_session_callbacks_new: %s", nghttp2_strerror(rv));
+    exit(EXIT_FAILURE);
+  }
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      shared_callbacks, on_begin_headers_callback);
+  nghttp2_session_callbacks_set_on_header_callback(shared_callbacks,
+                                                   on_header_callback);
+  nghttp2_session_callbacks_set_on_frame_recv_callback(shared_callbacks,
+                                                       on_frame_recv_callback);
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      shared_callbacks, on_stream_close_callback);
+  nghttp2_session_callbacks_set_on_frame_not_send_callback(
+      shared_callbacks, on_frame_not_send_callback);
+
+  rv = nghttp2_option_new(&shared_option);
+  if (rv != 0) {
+    fprintf(stderr, "nghttp2_option_new: %s", nghttp2_strerror(rv));
+    exit(EXIT_FAILURE);
+  }
+
+  nghttp2_option_set_recv_client_preface(shared_option, 1);
+
+  rv = io_loop_add(&loop, serv.fd, EPOLLIN, &serv);
+
+  if (rv != 0) {
+    exit(EXIT_FAILURE);
+  }
+
+  rv = io_loop_add(&loop, tmr.fd, EPOLLIN, &tmr);
+
+  if (rv != 0) {
+    exit(EXIT_FAILURE);
+  }
+
+  update_date();
+
+  io_loop_run(&loop, &serv);
+
+  nghttp2_option_del(shared_option);
+  nghttp2_session_callbacks_del(shared_callbacks);
+
+  return 0;
+}
diff --git a/fedora/spdylay.spec b/fedora/spdylay.spec
new file mode 100644 (file)
index 0000000..967d908
--- /dev/null
@@ -0,0 +1,75 @@
+Prefix: %{_usr}
+Name: spdylay
+Version: 0.3.7
+Release: 1%{?dist}
+Summary: The experimental SPDY protocol version 2 and 3 implementation in C
+
+Group: System Environment/Libraries
+License: MIT
+URL: http://sourceforge.net/projects/spdylay/
+Source0: %{name}-%{version}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+BuildRequires: pkgconfig >= 0.20, zlib >= 1.2.3, gcc, gcc-c++, make
+BuildRequires: openssl-devel, CUnit-devel
+
+#Requires:
+
+%description
+This is an experimental implementation of Google's SPDY protocol in C.
+This library provides SPDY version 2 and 3 framing layer implementation. It does not
+perform any I/O operations. When the library needs them, it calls the callback functions
+provided by the application. It also does not include any event polling mechanism,
+so the application can freely choose the way of handling events. This library code does
+not depend on any particular SSL library (except for example programs which depend on
+OpenSSL 1.0.1 or later).
+
+%package devel
+Summary: Development files for %{name}
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}
+
+%description devel
+The %{name}-devel package contains libraries and header files for
+developing applications that use %{name}.
+
+%prep
+%setup -q
+
+%build
+autoreconf -i
+%{__automake}
+%{__autoconf}
+%configure --disable-static --enable-examples --disable-xmltest
+%{__make} %{?_smp_mflags}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+%{__make} install DESTDIR=$RPM_BUILD_ROOT
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
+
+%files
+%defattr(-,root,root,-)
+%doc
+%{_libdir}/*.so.*
+%exclude %{_libdir}/*.la
+%{_bindir}/shrpx
+%{_bindir}/spdycat
+%{_bindir}/spdyd
+
+%files devel
+%defattr(-,root,root,-)
+%doc %{_docdir}/%{name}
+%{_includedir}/*
+%{_libdir}/*.so
+%{_libdir}/pkgconfig/*.pc
+
+%changelog
+* Sat Oct 27 2012 Raul Gutierrez Segales <rgs@itevenworks.net> 0.3.7-DEV
+- Initial RPM release.
diff --git a/gendowncasetbl.py b/gendowncasetbl.py
new file mode 100755 (executable)
index 0000000..aa98ae5
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+import sys
+
+def name(i):
+    if i < 0x20:
+        return \
+            ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ',
+             'BS  ', 'HT  ', 'LF  ', 'VT  ', 'FF  ', 'CR  ', 'SO  ', 'SI  ',
+             'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ',
+             'CAN ', 'EM  ', 'SUB ', 'ESC ', 'FS  ', 'GS  ', 'RS  ', 'US  '][i]
+    elif i == 0x7f:
+        return 'DEL '
+
+for i in range(256):
+    if chr(i) == ' ':
+        sys.stdout.write('{} /* SPC  */, '.format(i))
+    elif chr(i) == '\t':
+        sys.stdout.write('{} /* HT   */, '.format(i))
+    elif 'A' <= chr(i) and chr(i) <= 'Z':
+        sys.stdout.write('{} /* {}    */, '.format(i - ord('A') + ord('a'), chr(i)))
+    elif (0x21 <= i and i < 0x7f):
+        sys.stdout.write('{} /* {}    */, '.format(i, chr(i)))
+    elif 0x80 <= i:
+        sys.stdout.write('{} /* {} */, '.format(i, hex(i)))
+    elif 0 == i:
+        sys.stdout.write('{} /* NUL  */, '.format(i))
+    else:
+        sys.stdout.write('{} /* {} */, '.format(i, name(i)))
+    if (i + 1)%4 == 0:
+        sys.stdout.write('\n')
diff --git a/genheaderfunc.py b/genheaderfunc.py
new file mode 100755 (executable)
index 0000000..d8111d4
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+HEADERS = [
+    ':authority',
+    ':method',
+    ':path',
+    ':scheme',
+    ':status',
+    ':host', # for spdy
+    'expect',
+    'host',
+    'if-modified-since',
+    "te",
+    "cookie",
+    "http2-settings",
+    "server",
+    "via",
+    "x-forwarded-for",
+    "x-forwarded-proto",
+    "alt-svc",
+    "content-length",
+    "location",
+    "trailer",
+    "link",
+    "accept-encoding",
+    "accept-language",
+    "cache-control",
+    "user-agent",
+    # disallowed h1 headers
+    'connection',
+    'keep-alive',
+    'proxy-connection',
+    'transfer-encoding',
+    'upgrade'
+]
+
+def to_enum_hd(k):
+    res = 'HD_'
+    for c in k.upper():
+        if c == ':' or c == '-':
+            res += '_'
+            continue
+        res += c
+    return res
+
+def build_header(headers):
+    res = {}
+    for k in headers:
+        size = len(k)
+        if size not in res:
+            res[size] = {}
+        ent = res[size]
+        c = k[-1]
+        if c not in ent:
+            ent[c] = []
+        ent[c].append(k)
+
+    return res
+
+def gen_enum():
+    print '''\
+enum {'''
+    for k in sorted(HEADERS):
+        print '''\
+  {},'''.format(to_enum_hd(k))
+    print '''\
+  HD_MAXIDX,
+};'''
+
+def gen_index_header():
+    print '''\
+int lookup_token(const uint8_t *name, size_t namelen) {
+  switch (namelen) {'''
+    b = build_header(HEADERS)
+    for size in sorted(b.keys()):
+        ents = b[size]
+        print '''\
+  case {}:'''.format(size)
+        print '''\
+    switch (name[namelen - 1]) {'''
+        for c in sorted(ents.keys()):
+            headers = sorted(ents[c])
+            print '''\
+    case '{}':'''.format(c)
+            for k in headers:
+                print '''\
+      if (util::streq("{}", name, {})) {{
+        return {};
+      }}'''.format(k[:-1], size - 1, to_enum_hd(k))
+            print '''\
+      break;'''
+        print '''\
+    }
+    break;'''
+    print '''\
+  }
+  return -1;
+}'''
+
+if __name__ == '__main__':
+    gen_enum()
+    print ''
+    gen_index_header()
diff --git a/gennmchartbl.py b/gennmchartbl.py
new file mode 100755 (executable)
index 0000000..d04b92c
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+import sys
+
+def name(i):
+    if i < 0x21:
+        return \
+            ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ',
+             'BS  ', 'HT  ', 'LF  ', 'VT  ', 'FF  ', 'CR  ', 'SO  ', 'SI  ',
+             'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ',
+             'CAN ', 'EM  ', 'SUB ', 'ESC ', 'FS  ', 'GS  ', 'RS  ', 'US  ',
+             'SPC '][i]
+    elif i == 0x7f:
+        return 'DEL '
+
+for i in range(256):
+    if chr(i) in ["!" , "#" , "$" , "%" , "&" , "'" , "*",
+                  "+" , "-" , "." , "^" , "_" , "`" , "|" , "~"] or\
+        ('0' <= chr(i) and chr(i) <= '9') or \
+        ('a' <= chr(i) and chr(i) <= 'z'):
+        sys.stdout.write('1 /* {}    */, '.format(chr(i)))
+    elif (0x21 <= i and i < 0x7f):
+        sys.stdout.write('0 /* {}    */, '.format(chr(i)))
+    elif 0x80 <= i:
+        sys.stdout.write('0 /* {} */, '.format(hex(i)))
+    else:
+        sys.stdout.write('0 /* {} */, '.format(name(i)))
+    if (i + 1)%4 == 0:
+        sys.stdout.write('\n')
diff --git a/genvchartbl.py b/genvchartbl.py
new file mode 100755 (executable)
index 0000000..a70a881
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+import sys
+
+def name(i):
+    if i < 0x20:
+        return \
+            ['NUL ', 'SOH ', 'STX ', 'ETX ', 'EOT ', 'ENQ ', 'ACK ', 'BEL ',
+             'BS  ', 'HT  ', 'LF  ', 'VT  ', 'FF  ', 'CR  ', 'SO  ', 'SI  ',
+             'DLE ', 'DC1 ', 'DC2 ', 'DC3 ', 'DC4 ', 'NAK ', 'SYN ', 'ETB ',
+             'CAN ', 'EM  ', 'SUB ', 'ESC ', 'FS  ', 'GS  ', 'RS  ', 'US  '][i]
+    elif i == 0x7f:
+        return 'DEL '
+
+for i in range(256):
+    if chr(i) == ' ':
+        sys.stdout.write('1 /* SPC  */, ')
+    elif chr(i) == '\t':
+        sys.stdout.write('1 /* HT   */, ')
+    elif (0x21 <= i and i < 0x7f):
+        sys.stdout.write('1 /* {}    */, '.format(chr(i)))
+    elif 0x80 <= i:
+        sys.stdout.write('1 /* {} */, '.format(hex(i)))
+    elif 0 == i:
+        sys.stdout.write('1 /* NUL  */, ')
+    else:
+        sys.stdout.write('0 /* {} */, '.format(name(i)))
+    if (i + 1)%4 == 0:
+        sys.stdout.write('\n')
diff --git a/git-clang-format b/git-clang-format
new file mode 100755 (executable)
index 0000000..6a0db27
--- /dev/null
@@ -0,0 +1,484 @@
+#!/usr/bin/env python
+#
+#===- git-clang-format - ClangFormat Git Integration ---------*- python -*--===#
+#
+#                     The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+r"""                                                                             
+clang-format git integration                                                     
+============================                                                     
+                                                                                 
+This file provides a clang-format integration for git. Put it somewhere in your  
+path and ensure that it is executable. Then, "git clang-format" will invoke      
+clang-format on the changes in current files or a specific commit.               
+                                                                                 
+For further details, run:                                                        
+git clang-format -h                                                              
+                                                                                 
+Requires Python 2.7                                                              
+"""               
+
+import argparse
+import collections
+import contextlib
+import errno
+import os
+import re
+import subprocess
+import sys
+
+usage = 'git clang-format [OPTIONS] [<commit>] [--] [<file>...]'
+
+desc = '''
+Run clang-format on all lines that differ between the working directory
+and <commit>, which defaults to HEAD.  Changes are only applied to the working
+directory.
+
+The following git-config settings set the default of the corresponding option:
+  clangFormat.binary
+  clangFormat.commit
+  clangFormat.extension
+  clangFormat.style
+'''
+
+# Name of the temporary index file in which save the output of clang-format.
+# This file is created within the .git directory.
+temp_index_basename = 'clang-format-index'
+
+
+Range = collections.namedtuple('Range', 'start, count')
+
+
+def main():
+  config = load_git_config()
+
+  # In order to keep '--' yet allow options after positionals, we need to
+  # check for '--' ourselves.  (Setting nargs='*' throws away the '--', while
+  # nargs=argparse.REMAINDER disallows options after positionals.)
+  argv = sys.argv[1:]
+  try:
+    idx = argv.index('--')
+  except ValueError:
+    dash_dash = []
+  else:
+    dash_dash = argv[idx:]
+    argv = argv[:idx]
+
+  default_extensions = ','.join([
+      # From clang/lib/Frontend/FrontendOptions.cpp, all lower case
+      'c', 'h',  # C
+      'm',  # ObjC
+      'mm',  # ObjC++
+      'cc', 'cp', 'cpp', 'c++', 'cxx', 'hpp',  # C++
+      # Other languages that clang-format supports
+      'proto', 'protodevel',  # Protocol Buffers
+      'js',  # JavaScript
+      ])
+
+  p = argparse.ArgumentParser(
+    usage=usage, formatter_class=argparse.RawDescriptionHelpFormatter,
+    description=desc)
+  p.add_argument('--binary',
+                 default=config.get('clangformat.binary', 'clang-format'),
+                 help='path to clang-format'),
+  p.add_argument('--commit',
+                 default=config.get('clangformat.commit', 'HEAD'),
+                 help='default commit to use if none is specified'),
+  p.add_argument('--diff', action='store_true',
+                 help='print a diff instead of applying the changes')
+  p.add_argument('--extensions',
+                 default=config.get('clangformat.extensions',
+                                    default_extensions),
+                 help=('comma-separated list of file extensions to format, '
+                       'excluding the period and case-insensitive')),
+  p.add_argument('-f', '--force', action='store_true',
+                 help='allow changes to unstaged files')
+  p.add_argument('-p', '--patch', action='store_true',
+                 help='select hunks interactively')
+  p.add_argument('-q', '--quiet', action='count', default=0,
+                 help='print less information')
+  p.add_argument('--style',
+                 default=config.get('clangformat.style', None),
+                 help='passed to clang-format'),
+  p.add_argument('-v', '--verbose', action='count', default=0,
+                 help='print extra information')
+  # We gather all the remaining positional arguments into 'args' since we need
+  # to use some heuristics to determine whether or not <commit> was present.
+  # However, to print pretty messages, we make use of metavar and help.
+  p.add_argument('args', nargs='*', metavar='<commit>',
+                 help='revision from which to compute the diff')
+  p.add_argument('ignored', nargs='*', metavar='<file>...',
+                 help='if specified, only consider differences in these files')
+  opts = p.parse_args(argv)
+
+  opts.verbose -= opts.quiet
+  del opts.quiet
+
+  commit, files = interpret_args(opts.args, dash_dash, opts.commit)
+  changed_lines = compute_diff_and_extract_lines(commit, files)
+  if opts.verbose >= 1:
+    ignored_files = set(changed_lines)
+  filter_by_extension(changed_lines, opts.extensions.lower().split(','))
+  if opts.verbose >= 1:
+    ignored_files.difference_update(changed_lines)
+    if ignored_files:
+      print 'Ignoring changes in the following files (wrong extension):'
+      for filename in ignored_files:
+        print '   ', filename
+    if changed_lines:
+      print 'Running clang-format on the following files:'
+      for filename in changed_lines:
+        print '   ', filename
+  if not changed_lines:
+    print 'no modified files to format'
+    return
+  # The computed diff outputs absolute paths, so we must cd before accessing
+  # those files.
+  cd_to_toplevel()
+  old_tree = create_tree_from_workdir(changed_lines)
+  new_tree = run_clang_format_and_save_to_tree(changed_lines,
+                                               binary=opts.binary,
+                                               style=opts.style)
+  if opts.verbose >= 1:
+    print 'old tree:', old_tree
+    print 'new tree:', new_tree
+  if old_tree == new_tree:
+    if opts.verbose >= 0:
+      print 'clang-format did not modify any files'
+  elif opts.diff:
+    print_diff(old_tree, new_tree)
+  else:
+    changed_files = apply_changes(old_tree, new_tree, force=opts.force,
+                                  patch_mode=opts.patch)
+    if (opts.verbose >= 0 and not opts.patch) or opts.verbose >= 1:
+      print 'changed files:'
+      for filename in changed_files:
+        print '   ', filename
+
+
+def load_git_config(non_string_options=None):
+  """Return the git configuration as a dictionary.
+
+  All options are assumed to be strings unless in `non_string_options`, in which
+  is a dictionary mapping option name (in lower case) to either "--bool" or
+  "--int"."""
+  if non_string_options is None:
+    non_string_options = {}
+  out = {}
+  for entry in run('git', 'config', '--list', '--null').split('\0'):
+    if entry:
+      name, value = entry.split('\n', 1)
+      if name in non_string_options:
+        value = run('git', 'config', non_string_options[name], name)
+      out[name] = value
+  return out
+
+
+def interpret_args(args, dash_dash, default_commit):
+  """Interpret `args` as "[commit] [--] [files...]" and return (commit, files).
+
+  It is assumed that "--" and everything that follows has been removed from
+  args and placed in `dash_dash`.
+
+  If "--" is present (i.e., `dash_dash` is non-empty), the argument to its
+  left (if present) is taken as commit.  Otherwise, the first argument is
+  checked if it is a commit or a file.  If commit is not given,
+  `default_commit` is used."""
+  if dash_dash:
+    if len(args) == 0:
+      commit = default_commit
+    elif len(args) > 1:
+      die('at most one commit allowed; %d given' % len(args))
+    else:
+      commit = args[0]
+    object_type = get_object_type(commit)
+    if object_type not in ('commit', 'tag'):
+      if object_type is None:
+        die("'%s' is not a commit" % commit)
+      else:
+        die("'%s' is a %s, but a commit was expected" % (commit, object_type))
+    files = dash_dash[1:]
+  elif args:
+    if disambiguate_revision(args[0]):
+      commit = args[0]
+      files = args[1:]
+    else:
+      commit = default_commit
+      files = args
+  else:
+    commit = default_commit
+    files = []
+  return commit, files
+
+
+def disambiguate_revision(value):
+  """Returns True if `value` is a revision, False if it is a file, or dies."""
+  # If `value` is ambiguous (neither a commit nor a file), the following
+  # command will die with an appropriate error message.
+  run('git', 'rev-parse', value, verbose=False)
+  object_type = get_object_type(value)
+  if object_type is None:
+    return False
+  if object_type in ('commit', 'tag'):
+    return True
+  die('`%s` is a %s, but a commit or filename was expected' %
+      (value, object_type))
+
+
+def get_object_type(value):
+  """Returns a string description of an object's type, or None if it is not
+  a valid git object."""
+  cmd = ['git', 'cat-file', '-t', value]
+  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  stdout, stderr = p.communicate()
+  if p.returncode != 0:
+    return None
+  return stdout.strip()
+
+
+def compute_diff_and_extract_lines(commit, files):
+  """Calls compute_diff() followed by extract_lines()."""
+  diff_process = compute_diff(commit, files)
+  changed_lines = extract_lines(diff_process.stdout)
+  diff_process.stdout.close()
+  diff_process.wait()
+  if diff_process.returncode != 0:
+    # Assume error was already printed to stderr.
+    sys.exit(2)
+  return changed_lines
+
+
+def compute_diff(commit, files):
+  """Return a subprocess object producing the diff from `commit`.
+
+  The return value's `stdin` file object will produce a patch with the
+  differences between the working directory and `commit`, filtered on `files`
+  (if non-empty).  Zero context lines are used in the patch."""
+  cmd = ['git', 'diff-index', '-p', '-U0', commit, '--']
+  cmd.extend(files)
+  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+  p.stdin.close()
+  return p
+
+
+def extract_lines(patch_file):
+  """Extract the changed lines in `patch_file`.
+
+  The return value is a dictionary mapping filename to a list of (start_line,
+  line_count) pairs.
+
+  The input must have been produced with ``-U0``, meaning unidiff format with
+  zero lines of context.  The return value is a dict mapping filename to a
+  list of line `Range`s."""
+  matches = {}
+  for line in patch_file:
+    match = re.search(r'^\+\+\+\ [^/]+/(.*)', line)
+    if match:
+      filename = match.group(1).rstrip('\r\n')
+    match = re.search(r'^@@ -[0-9,]+ \+(\d+)(,(\d+))?', line)
+    if match:
+      start_line = int(match.group(1))
+      line_count = 1
+      if match.group(3):
+        line_count = int(match.group(3))
+      if line_count > 0:
+        matches.setdefault(filename, []).append(Range(start_line, line_count))
+  return matches
+
+
+def filter_by_extension(dictionary, allowed_extensions):
+  """Delete every key in `dictionary` that doesn't have an allowed extension.
+
+  `allowed_extensions` must be a collection of lowercase file extensions,
+  excluding the period."""
+  allowed_extensions = frozenset(allowed_extensions)
+  for filename in dictionary.keys():
+    base_ext = filename.rsplit('.', 1)
+    if len(base_ext) == 1 or base_ext[1].lower() not in allowed_extensions:
+      del dictionary[filename]
+
+
+def cd_to_toplevel():
+  """Change to the top level of the git repository."""
+  toplevel = run('git', 'rev-parse', '--show-toplevel')
+  os.chdir(toplevel)
+
+
+def create_tree_from_workdir(filenames):
+  """Create a new git tree with the given files from the working directory.
+
+  Returns the object ID (SHA-1) of the created tree."""
+  return create_tree(filenames, '--stdin')
+
+
+def run_clang_format_and_save_to_tree(changed_lines, binary='clang-format',
+                                      style=None):
+  """Run clang-format on each file and save the result to a git tree.
+
+  Returns the object ID (SHA-1) of the created tree."""
+  def index_info_generator():
+    for filename, line_ranges in changed_lines.iteritems():
+      mode = oct(os.stat(filename).st_mode)
+      blob_id = clang_format_to_blob(filename, line_ranges, binary=binary,
+                                     style=style)
+      yield '%s %s\t%s' % (mode, blob_id, filename)
+  return create_tree(index_info_generator(), '--index-info')
+
+
+def create_tree(input_lines, mode):
+  """Create a tree object from the given input.
+
+  If mode is '--stdin', it must be a list of filenames.  If mode is
+  '--index-info' is must be a list of values suitable for "git update-index
+  --index-info", such as "<mode> <SP> <sha1> <TAB> <filename>".  Any other mode
+  is invalid."""
+  assert mode in ('--stdin', '--index-info')
+  cmd = ['git', 'update-index', '--add', '-z', mode]
+  with temporary_index_file():
+    p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
+    for line in input_lines:
+      p.stdin.write('%s\0' % line)
+    p.stdin.close()
+    if p.wait() != 0:
+      die('`%s` failed' % ' '.join(cmd))
+    tree_id = run('git', 'write-tree')
+    return tree_id
+
+
+def clang_format_to_blob(filename, line_ranges, binary='clang-format',
+                         style=None):
+  """Run clang-format on the given file and save the result to a git blob.
+
+  Returns the object ID (SHA-1) of the created blob."""
+  clang_format_cmd = [binary, filename]
+  if style:
+    clang_format_cmd.extend(['-style='+style])
+  clang_format_cmd.extend([
+      '-lines=%s:%s' % (start_line, start_line+line_count-1)
+      for start_line, line_count in line_ranges])
+  try:
+    clang_format = subprocess.Popen(clang_format_cmd, stdin=subprocess.PIPE,
+                                    stdout=subprocess.PIPE)
+  except OSError as e:
+    if e.errno == errno.ENOENT:
+      die('cannot find executable "%s"' % binary)
+    else:
+      raise
+  clang_format.stdin.close()
+  hash_object_cmd = ['git', 'hash-object', '-w', '--path='+filename, '--stdin']
+  hash_object = subprocess.Popen(hash_object_cmd, stdin=clang_format.stdout,
+                                 stdout=subprocess.PIPE)
+  clang_format.stdout.close()
+  stdout = hash_object.communicate()[0]
+  if hash_object.returncode != 0:
+    die('`%s` failed' % ' '.join(hash_object_cmd))
+  if clang_format.wait() != 0:
+    die('`%s` failed' % ' '.join(clang_format_cmd))
+  return stdout.rstrip('\r\n')
+
+
+@contextlib.contextmanager
+def temporary_index_file(tree=None):
+  """Context manager for setting GIT_INDEX_FILE to a temporary file and deleting
+  the file afterward."""
+  index_path = create_temporary_index(tree)
+  old_index_path = os.environ.get('GIT_INDEX_FILE')
+  os.environ['GIT_INDEX_FILE'] = index_path
+  try:
+    yield
+  finally:
+    if old_index_path is None:
+      del os.environ['GIT_INDEX_FILE']
+    else:
+      os.environ['GIT_INDEX_FILE'] = old_index_path
+    os.remove(index_path)
+
+
+def create_temporary_index(tree=None):
+  """Create a temporary index file and return the created file's path.
+
+  If `tree` is not None, use that as the tree to read in.  Otherwise, an
+  empty index is created."""
+  gitdir = run('git', 'rev-parse', '--git-dir')
+  path = os.path.join(gitdir, temp_index_basename)
+  if tree is None:
+    tree = '--empty'
+  run('git', 'read-tree', '--index-output='+path, tree)
+  return path
+
+
+def print_diff(old_tree, new_tree):
+  """Print the diff between the two trees to stdout."""
+  # We use the porcelain 'diff' and not plumbing 'diff-tree' because the output
+  # is expected to be viewed by the user, and only the former does nice things
+  # like color and pagination.
+  subprocess.check_call(['git', 'diff', old_tree, new_tree, '--'])
+
+
+def apply_changes(old_tree, new_tree, force=False, patch_mode=False):
+  """Apply the changes in `new_tree` to the working directory.
+
+  Bails if there are local changes in those files and not `force`.  If
+  `patch_mode`, runs `git checkout --patch` to select hunks interactively."""
+  changed_files = run('git', 'diff-tree', '-r', '-z', '--name-only', old_tree,
+                      new_tree).rstrip('\0').split('\0')
+  if not force:
+    unstaged_files = run('git', 'diff-files', '--name-status', *changed_files)
+    if unstaged_files:
+      print >>sys.stderr, ('The following files would be modified but '
+                           'have unstaged changes:')
+      print >>sys.stderr, unstaged_files
+      print >>sys.stderr, 'Please commit, stage, or stash them first.'
+      sys.exit(2)
+  if patch_mode:
+    # In patch mode, we could just as well create an index from the new tree
+    # and checkout from that, but then the user will be presented with a
+    # message saying "Discard ... from worktree".  Instead, we use the old
+    # tree as the index and checkout from new_tree, which gives the slightly
+    # better message, "Apply ... to index and worktree".  This is not quite
+    # right, since it won't be applied to the user's index, but oh well.
+    with temporary_index_file(old_tree):
+      subprocess.check_call(['git', 'checkout', '--patch', new_tree])
+    index_tree = old_tree
+  else:
+    with temporary_index_file(new_tree):
+      run('git', 'checkout-index', '-a', '-f')
+  return changed_files
+
+
+def run(*args, **kwargs):
+  stdin = kwargs.pop('stdin', '')
+  verbose = kwargs.pop('verbose', True)
+  strip = kwargs.pop('strip', True)
+  for name in kwargs:
+    raise TypeError("run() got an unexpected keyword argument '%s'" % name)
+  p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                       stdin=subprocess.PIPE)
+  stdout, stderr = p.communicate(input=stdin)
+  if p.returncode == 0:
+    if stderr:
+      if verbose:
+        print >>sys.stderr, '`%s` printed to stderr:' % ' '.join(args)
+      print >>sys.stderr, stderr.rstrip()
+    if strip:
+      stdout = stdout.rstrip('\r\n')
+    return stdout
+  if verbose:
+    print >>sys.stderr, '`%s` returned %s' % (' '.join(args), p.returncode)
+  if stderr:
+    print >>sys.stderr, stderr.rstrip()
+  sys.exit(2)
+
+
+def die(message):
+  print >>sys.stderr, 'error:', message
+  sys.exit(2)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/help2rst.py b/help2rst.py
new file mode 100755 (executable)
index 0000000..891decf
--- /dev/null
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# script to produce rst file from program's help output.
+
+from __future__ import unicode_literals
+import sys
+import re
+import argparse
+
+arg_indent = ' ' * 14
+
+def help2man(infile):
+    # We assume that first line is usage line like this:
+    #
+    # Usage: nghttp [OPTIONS]... URI...
+    #
+    # The second line is description of the command.  Multiple lines
+    # are permitted.  The blank line signals the end of this section.
+    # After that, we parses positional and optional arguments.
+    #
+    # The positional argument is enclosed with < and >:
+    #
+    # <PRIVATE_KEY>
+    #
+    # We may describe default behavior without any options by encoding
+    # ( and ):
+    #
+    # (default mode)
+    #
+    # "Options:" is treated specially and produces "OPTIONS" section.
+    # We allow subsection under OPTIONS.  Lines not starting with (, <
+    # and Options: are treated as subsection name and produces section
+    # one level down:
+    #
+    # TLS/SSL:
+    #
+    # The above is an example of subsection.
+    #
+    # The description of arguments must be indented by len(arg_indent)
+    # characters.  The default value should be placed in separate line
+    # and should be start with "Default: " after indentation.
+
+    line = infile.readline().strip()
+    m = re.match(r'^Usage: (.*)', line)
+    if not m:
+        print 'usage line is invalid.  Expected following lines:'
+        print 'Usage: cmdname ...'
+        sys.exit(1)
+    synopsis = m.group(1).split(' ', 1)
+    if len(synopsis) == 2:
+        cmdname, args = synopsis
+    else:
+        cmdname, args = synopsis[0], ''
+
+    description = []
+    for line in infile:
+        line = line.strip()
+        if not line:
+            break
+        description.append(line)
+
+    print '''
+{cmdname}(1)
+{cmdnameunderline}
+
+SYNOPSIS
+--------
+
+**{cmdname}** {args}
+
+DESCRIPTION
+-----------
+
+{description}
+'''.format(cmdname=cmdname, args=args,
+           cmdnameunderline='=' * (len(cmdname) + 3),
+           synopsis=synopsis, description=format_text('\n'.join(description)))
+
+    in_arg = False
+
+    for line in infile:
+        line = line.rstrip()
+
+        if not line.strip() and in_arg:
+            print ''
+            continue
+        if line.startswith('   ') and in_arg:
+            if not line.startswith(arg_indent):
+                sys.stderr.write('warning: argument description is not indented correctly.  We need {} spaces as indentation.\n'.format(len(arg_indent)))
+            print '{}'.format(format_arg_text(line[len(arg_indent):]))
+            continue
+
+        if in_arg:
+            print ''
+            in_arg = False
+
+        if line == 'Options:':
+            print 'OPTIONS'
+            print '-------'
+            print ''
+            continue
+
+        if line.startswith('  <'):
+            # positional argument
+            m = re.match(r'^(?:\s+)([a-zA-Z0-9-_<>]+)(.*)', line)
+            argname, rest = m.group(1), m.group(2)
+            print '.. describe:: {}'.format(argname)
+            print ''
+            print '{}'.format(format_arg_text(rest.strip()))
+            in_arg = True
+            continue
+
+        if line.startswith('  ('):
+            # positional argument
+            m = re.match(r'^(?:\s+)(\([a-zA-Z0-9-_<> ]+\))(.*)', line)
+            argname, rest = m.group(1), m.group(2)
+            print '.. describe:: {}'.format(argname)
+            print ''
+            print '{}'.format(format_arg_text(rest.strip()))
+            in_arg = True
+            continue
+
+        if line.startswith('  -'):
+            # optional argument
+            m = re.match(
+                r'^(?:\s+)(-\S+?(?:, -\S+?)*)($| .*)',
+                line)
+            argname, rest = m.group(1), m.group(2)
+            print '.. option:: {}'.format(argname)
+            print ''
+            rest = rest.strip()
+            if len(rest):
+                print '{}'.format(format_arg_text(rest))
+            in_arg = True
+            continue
+
+        if not line.startswith(' ') and line.endswith(':'):
+            # subsection
+            subsec = line.strip()[:-1]
+            print '{}'.format(subsec)
+            print '{}'.format('~' * len(subsec))
+            print ''
+            continue
+
+        print line.strip()
+
+def format_text(text):
+    # escape *
+    if len(text) > len(arg_indent):
+        text = text[:len(arg_indent) + 1] + re.sub(r'\*', r'\*', text[len(arg_indent) + 1:])
+    else:
+        text = re.sub(r'\*', r'\*', text)
+    # markup option reference
+    text = re.sub(r'(^|\s)(-[a-zA-Z0-9-]+)', r'\1:option:`\2`', text)
+    # sphinx does not like markup like ':option:`-f`='.  We need
+    # backslash between ` and =.
+    text = re.sub(r'(:option:`.*?`)(\S)', r'\1\\\2', text)
+    # file path should be italic
+    text = re.sub(r'(^|\s|\'|")(/[^\s\'"]*)', r'\1*\2*', text)
+    return text
+
+def format_arg_text(text):
+    if text.strip().startswith('Default: '):
+        return '\n    ' + re.sub(r'^(\s*Default: )(.*)$', r'\1``\2``', text)
+    return '    {}'.format(format_text(text))
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        description='Produces rst document from help output.')
+    parser.add_argument('-i', '--include', metavar='FILE',
+                        help='include content of <FILE> as verbatim.  It should be ReST formatted text.')
+    args = parser.parse_args()
+    help2man(sys.stdin)
+    if args.include:
+        print ''
+        with open(args.include) as f:
+            sys.stdout.write(f.read())
diff --git a/integration-tests/Makefile.am b/integration-tests/Makefile.am
new file mode 100644 (file)
index 0000000..f03cd78
--- /dev/null
@@ -0,0 +1,43 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2015 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+EXTRA_DIST = \
+       nghttpx_http1_test.go \
+       nghttpx_http2_test.go \
+       nghttpx_spdy_test.go \
+       server_tester.go \
+       server.key \
+       server.crt \
+       alt-server.key \
+       alt-server.crt \
+       setenv
+
+.PHONY: itprep it
+
+itprep:
+       go get -d -v github.com/bradfitz/http2
+       go get -d -v github.com/tatsuhiro-t/go-nghttp2
+       go get -d -v golang.org/x/net/spdy
+
+it:
+       sh setenv go test -v
diff --git a/integration-tests/alt-server.crt b/integration-tests/alt-server.crt
new file mode 100644 (file)
index 0000000..f003eb1
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAm+gAwIBAgIJANfuEldiquMNMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMCmFsdC1kb21haW4wHhcNMTUwMTI1MDYy
+NTQxWhcNMjUwMTIyMDYyNTQxWjBaMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29t
+ZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYD
+VQQDDAphbHQtZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+0IwhDOGDipGrJQ9IoRSzPdkU/Ii4aJgGKHlXminym42X0VI3IW61RLvOHRlHVmVH
+JQjFuDo2x+y81t9NlDg3HGUbSpzOzpm6StiutB7c4hreT5G4r0YKya1ugiemN0+p
+qjIPJWm2jVnf448eZvUKRKEQ9W0MLZjiNjVGKrKlwo7fIlXg4N3+YixLYffAT1NV
+d1T6V5jzlbruj15gK2nGjMQ9D1h1t9vTbTxY+mtk72aX0Y64IE6pPBWLFSSH8ozU
+idDoL3AZwz2Jker+ALKK8CM4uho/RPpyW1C06HH+HLdH2MqEjDOROde/Nzxm668O
+gK/JWGIEyUqYiUXx0yhFxwIDAQABo1AwTjAdBgNVHQ4EFgQU/Y0GDN2uPjbyePcu
+95ZvYEK/gHIwHwYDVR0jBBgwFoAU/Y0GDN2uPjbyePcu95ZvYEK/gHIwDAYDVR0T
+BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAodD6LVCzL3wfsZ6TxTzf9TfgIdbj
+ilL3SEMT/xnfTXT3SLYScTRqQIAI29Y7dOLMq89p4hY2wmeUEhBUAz+y9G2JVr8o
+6EbxXrQpWgNJogELqoNnMdrDxB5RsmDDKEJ/rLjDfSkjWbK7B2PZsqVTDgjekCFw
+u6FqTIjn/O1O/L5tjwxwxjHmQod/maFCvXoDOVBuwdHnkp298tqlvsHfHO8m++Wj
++XYB8plMIjpeTh9v4w9Jc4QZ59lK/3Tt4qaENeQrMEubKSY/Zen7L2bzhk+cChWT
+GSGz9uNXieoZaH79D0wnyZaSZ5Ds4ActMevnGg3iYXuzuFqx8Pungn74Vg==
+-----END CERTIFICATE-----
diff --git a/integration-tests/alt-server.key b/integration-tests/alt-server.key
new file mode 100644 (file)
index 0000000..a977663
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDQjCEM4YOKkasl
+D0ihFLM92RT8iLhomAYoeVeaKfKbjZfRUjchbrVEu84dGUdWZUclCMW4OjbH7LzW
+302UODccZRtKnM7OmbpK2K60HtziGt5PkbivRgrJrW6CJ6Y3T6mqMg8labaNWd/j
+jx5m9QpEoRD1bQwtmOI2NUYqsqXCjt8iVeDg3f5iLEth98BPU1V3VPpXmPOVuu6P
+XmAracaMxD0PWHW329NtPFj6a2TvZpfRjrggTqk8FYsVJIfyjNSJ0OgvcBnDPYmR
+6v4AsorwIzi6Gj9E+nJbULTocf4ct0fYyoSMM5E51783PGbrrw6Ar8lYYgTJSpiJ
+RfHTKEXHAgMBAAECggEBALTrjFSXY72YB+h7rN+JjMIwDIPUvF6I3HbKZhQpJf6K
+xNVkRM2tNHavku0tm/S4ohLf3F+pqRKiL2Udjjjy1+S7VgTRqpwTQ0lhV5aNW8SP
+2KMg4R61XfB+k+s4KHu9kYxEJ12mqydPe+r3o0FgfYryTDsOYk1AX6b1aqzqFOGF
+7GaqLALSbKU59tcJJ1SZNBbpIKFUrAT9nZt9dW02/foqP5bzUk43Yjw48xmLwegc
+bMXXcpZhNZSktltvwRw7Q4Foc9kuRlMdTAnAD9PnMCcZwicS/YeVVF6Rz4fGviKv
+7/kPHQ7g4YpFktVDzuZ5xw6GDVFeJ6uGMVUX8+EePvkCgYEA+/nrcn82nFHCxm8Q
+0iiUhi/AoXjZg+O5Ytaje9O/YNoX+c4ywe13h0+TXKH79O0KfTwXeJyDgPZbAIFV
+9oURellRYUzKDafnBHis2f+Ywn6GqHL5e2X30ZxIp1GK46pcvne1YuvJhgGmiVay
+vd7sRx09OKU124dG22rIFCis6asCgYEA0+CsA6LrEwQ/aPJYASY3VHNO/WoAOnPg
+Cwsg+02XWsPEwP//lNmpanz8TUm2URS063ZK8bx7t3ejvDgBdsRwwjiMlDp7XTUU
+3Zk+mhCV2qkMi02aKemvz29bDhmh5JoH7W3IwsXtJYO0yZDYrDR3ioiKRccioPoE
+b/Nq781sEFUCgYEA4xqx9xRpaCLY5nicNI6WrwrDF8YQZisNn+PMnYKP7v8itOgA
+H4GkRbSXINpueKZc2dsbXH3UmJtyEdaAYBw3UIrIKmZHhl9afFE3mZQhXssjGxfl
+fC6/WZD+eq+n+uJFjPXf6jSSAdHjA828dB1D4CSeVTuyexZF6uUnR+QRVNkCgYEA
+i+pb7XLSpZYygY03zFp+Q0h6KyKqz+7hTqmkuA8/GfMZpRHop1UtaWLsAeXhfZ2c
+87kEOKptUHSzLYIWhWWnyLorK1+LQ7vf8Y5XJso5C1KDNCKk4XSuYt94U9FddWa6
+QXI0F1s5BYL6Cfma++0R2+va08Vy+rbf40XtojoXWJkCgYEA0hMQSCvok7is27nQ
+G80KXfmghU2eEB7zif3T00/fwJycxEbmnNeof+SKmhdY4ZgqTscfOxlQPflV/eqB
+xs4GnFDDeM0F8KH0BimOXxr7sJPFCg22PCCQQcRtM/KoU+ip/kNmTfwrsC0xMFPU
+HD8M1JCZF2eLMekXXP3cB0U4sUs=
+-----END PRIVATE KEY-----
diff --git a/integration-tests/config.go.in b/integration-tests/config.go.in
new file mode 100644 (file)
index 0000000..0a6fd6b
--- /dev/null
@@ -0,0 +1,5 @@
+package nghttp2
+
+const (
+       buildDir = "@top_builddir@"
+)
diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go
new file mode 100644 (file)
index 0000000..d035f08
--- /dev/null
@@ -0,0 +1,405 @@
+package nghttp2
+
+import (
+       "bufio"
+       "fmt"
+       "github.com/bradfitz/http2/hpack"
+       "io"
+       "net/http"
+       "syscall"
+       "testing"
+)
+
+// TestH1H1PlainGET tests whether simple HTTP/1 GET request works.
+func TestH1H1PlainGET(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H1PlainGET",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+
+       want := 200
+       if got := res.status; got != want {
+               t.Errorf("status = %v; want %v", got, want)
+       }
+}
+
+// TestH1H1PlainGETClose tests whether simple HTTP/1 GET request with
+// Connetion: close request header field works.
+func TestH1H1PlainGETClose(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H1PlainGETClose",
+               header: []hpack.HeaderField{
+                       pair("Connection", "close"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+
+       want := 200
+       if got := res.status; got != want {
+               t.Errorf("status = %v; want %v", got, want)
+       }
+}
+
+// TestH1H1MultipleRequestCL tests that server rejects request which
+// contains multiple Content-Length header fields.
+func TestH1H1MultipleRequestCL(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Errorf("server should not forward bad request")
+       })
+       defer st.Close()
+
+       if _, err := io.WriteString(st.conn, fmt.Sprintf(`GET / HTTP/1.1
+Host: %v
+Test-Case: TestH1H1MultipleRequestCL
+Content-Length: 0
+Content-Length: 0
+
+`, st.authority)); err != nil {
+               t.Fatalf("Error io.WriteString() = %v", err)
+       }
+
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
+       if err != nil {
+               t.Fatalf("Error http.ReadResponse() = %v", err)
+       }
+
+       want := 400
+       if got := resp.StatusCode; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH1H1ConnectFailure tests that server handles the situation that
+// connection attempt to HTTP/1 backend failed.
+func TestH1H1ConnectFailure(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       // shutdown backend server to simulate backend connect failure
+       st.ts.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H1ConnectFailure",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       want := 503
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH1H1GracefulShutdown tests graceful shutdown.
+func TestH1H1GracefulShutdown(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H1GracefulShutdown-1",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+
+       st.cmd.Process.Signal(syscall.SIGQUIT)
+
+       res, err = st.http1(requestParam{
+               name: "TestH1H1GracefulShutdown-2",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+
+       if got, want := res.connClose, true; got != want {
+               t.Errorf("res.connClose: %v; want %v", got, want)
+       }
+
+       want := io.EOF
+       if _, err := st.conn.Read(nil); err == nil || err != want {
+               t.Errorf("st.conn.Read(): %v; want %v", err, want)
+       }
+}
+
+// TestH1H1HostRewrite tests that server rewrites Host header field
+func TestH1H1HostRewrite(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H1HostRewrite",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := res.header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH1H1HTTP10 tests that server can accept HTTP/1.0 request
+// without Host header field
+func TestH1H1HTTP10(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1HTTP10\r\n\r\n"); err != nil {
+               t.Fatalf("Error io.WriteString() = %v", err)
+       }
+
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
+       if err != nil {
+               t.Fatalf("Error http.ReadResponse() = %v", err)
+       }
+
+       if got, want := resp.StatusCode, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := resp.Header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH1H1HTTP10NoHostRewrite tests that server generates host header
+// field using actual backend server even if --no-http-rewrite is
+// used.
+func TestH1H1HTTP10NoHostRewrite(t *testing.T) {
+       st := newServerTester([]string{"--no-host-rewrite"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H1HTTP10NoHostRewrite\r\n\r\n"); err != nil {
+               t.Fatalf("Error io.WriteString() = %v", err)
+       }
+
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
+       if err != nil {
+               t.Fatalf("Error http.ReadResponse() = %v", err)
+       }
+
+       if got, want := resp.StatusCode, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := resp.Header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2ConnectFailure tests that server handles the situation that
+// connection attempt to HTTP/2 backend failed.
+func TestH1H2ConnectFailure(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, noopHandler)
+       defer st.Close()
+
+       // simulate backend connect attempt failure
+       st.ts.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H2ConnectFailure",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       want := 503
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2NoHost tests that server rejects request without Host
+// header field for HTTP/2 backend.
+func TestH1H2NoHost(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Errorf("server should not forward bad request")
+       })
+       defer st.Close()
+
+       // without Host header field, we expect 400 response
+       if _, err := io.WriteString(st.conn, "GET / HTTP/1.1\r\nTest-Case: TestH1H2NoHost\r\n\r\n"); err != nil {
+               t.Fatalf("Error io.WriteString() = %v", err)
+       }
+
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
+       if err != nil {
+               t.Fatalf("Error http.ReadResponse() = %v", err)
+       }
+
+       want := 400
+       if got := resp.StatusCode; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2HTTP10 tests that server can accept HTTP/1.0 request
+// without Host header field
+func TestH1H2HTTP10(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H2HTTP10\r\n\r\n"); err != nil {
+               t.Fatalf("Error io.WriteString() = %v", err)
+       }
+
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
+       if err != nil {
+               t.Fatalf("Error http.ReadResponse() = %v", err)
+       }
+
+       if got, want := resp.StatusCode, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := resp.Header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2HTTP10NoHostRewrite tests that server generates host header
+// field using actual backend server even if --no-http-rewrite is
+// used.
+func TestH1H2HTTP10NoHostRewrite(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge", "--no-host-rewrite"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       if _, err := io.WriteString(st.conn, "GET / HTTP/1.0\r\nTest-Case: TestH1H2HTTP10NoHostRewrite\r\n\r\n"); err != nil {
+               t.Fatalf("Error io.WriteString() = %v", err)
+       }
+
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), nil)
+       if err != nil {
+               t.Fatalf("Error http.ReadResponse() = %v", err)
+       }
+
+       if got, want := resp.StatusCode, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := resp.Header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2CrumbleCookie tests that Cookies are crumbled and assembled
+// when forwarding to HTTP/2 backend link.  go-nghttp2 server
+// concatenates crumbled Cookies automatically, so this test is not
+// much effective now.
+func TestH1H2CrumbleCookie(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want {
+                       t.Errorf("Cookie: %v; want %v", got, want)
+               }
+       })
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H2CrumbleCookie",
+               header: []hpack.HeaderField{
+                       pair("Cookie", "alpha; bravo; charlie"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2GenerateVia tests that server generates Via header field to and
+// from backend server.
+func TestH1H2GenerateVia(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "1.1 nghttpx"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+       })
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H2GenerateVia",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "2.0 nghttpx"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2AppendVia tests that server adds value to existing Via
+// header field to and from backend server.
+func TestH1H2AppendVia(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "foo, 1.1 nghttpx"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+               w.Header().Add("Via", "bar")
+       })
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H2AppendVia",
+               header: []hpack.HeaderField{
+                       pair("via", "foo"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "bar, 2.0 nghttpx"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestH1H2NoVia tests that server does not add value to existing Via
+// header field to and from backend server.
+func TestH1H2NoVia(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge", "--no-via"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "foo"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+               w.Header().Add("Via", "bar")
+       })
+       defer st.Close()
+
+       res, err := st.http1(requestParam{
+               name: "TestH1H2NoVia",
+               header: []hpack.HeaderField{
+                       pair("via", "foo"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http1() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "bar"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
diff --git a/integration-tests/nghttpx_http2_test.go b/integration-tests/nghttpx_http2_test.go
new file mode 100644 (file)
index 0000000..29e5cc8
--- /dev/null
@@ -0,0 +1,706 @@
+package nghttp2
+
+import (
+       "crypto/tls"
+       "fmt"
+       "github.com/bradfitz/http2"
+       "github.com/bradfitz/http2/hpack"
+       "io"
+       "io/ioutil"
+       "net/http"
+       "syscall"
+       "testing"
+)
+
+// TestH2H1PlainGET tests whether simple HTTP/2 GET request works.
+func TestH2H1PlainGET(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1PlainGET",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+
+       want := 200
+       if res.status != want {
+               t.Errorf("status = %v; want %v", res.status, want)
+       }
+}
+
+// TestH2H1AddXff tests that server generates X-Forwarded-For header
+// field when forwarding request to backend.
+func TestH2H1AddXff(t *testing.T) {
+       st := newServerTester([]string{"--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
+               xff := r.Header.Get("X-Forwarded-For")
+               want := "127.0.0.1"
+               if xff != want {
+                       t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
+               }
+       })
+       defer st.Close()
+
+       _, err := st.http2(requestParam{
+               name: "TestH2H1AddXff",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+}
+
+// TestH2H1AddXff2 tests that server appends X-Forwarded-For header
+// field to existing one when forwarding request to backend.
+func TestH2H1AddXff2(t *testing.T) {
+       st := newServerTester([]string{"--add-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
+               xff := r.Header.Get("X-Forwarded-For")
+               want := "host, 127.0.0.1"
+               if xff != want {
+                       t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
+               }
+       })
+       defer st.Close()
+
+       _, err := st.http2(requestParam{
+               name: "TestH2H1AddXff2",
+               header: []hpack.HeaderField{
+                       pair("x-forwarded-for", "host"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+}
+
+// TestH2H1StripXff tests that --strip-incoming-x-forwarded-for
+// option.
+func TestH2H1StripXff(t *testing.T) {
+       st := newServerTester([]string{"--strip-incoming-x-forwarded-for"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if xff, found := r.Header["X-Forwarded-For"]; found {
+                       t.Errorf("X-Forwarded-For = %v; want nothing", xff)
+               }
+       })
+       defer st.Close()
+
+       _, err := st.http2(requestParam{
+               name: "TestH2H1StripXff1",
+               header: []hpack.HeaderField{
+                       pair("x-forwarded-for", "host"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+}
+
+// TestH2H1StripAddXff tests that --strip-incoming-x-forwarded-for and
+// --add-x-forwarded-for options.
+func TestH2H1StripAddXff(t *testing.T) {
+       args := []string{
+               "--strip-incoming-x-forwarded-for",
+               "--add-x-forwarded-for",
+       }
+       st := newServerTester(args, t, func(w http.ResponseWriter, r *http.Request) {
+               xff := r.Header.Get("X-Forwarded-For")
+               want := "127.0.0.1"
+               if xff != want {
+                       t.Errorf("X-Forwarded-For = %v; want %v", xff, want)
+               }
+       })
+       defer st.Close()
+
+       _, err := st.http2(requestParam{
+               name: "TestH2H1StripAddXff",
+               header: []hpack.HeaderField{
+                       pair("x-forwarded-for", "host"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+}
+
+// TestH2H1GenerateVia tests that server generates Via header field to and
+// from backend server.
+func TestH2H1GenerateVia(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "2.0 nghttpx"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1GenerateVia",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1AppendVia tests that server adds value to existing Via
+// header field to and from backend server.
+func TestH2H1AppendVia(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "foo, 2.0 nghttpx"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+               w.Header().Add("Via", "bar")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1AppendVia",
+               header: []hpack.HeaderField{
+                       pair("via", "foo"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1NoVia tests that server does not add value to existing Via
+// header field to and from backend server.
+func TestH2H1NoVia(t *testing.T) {
+       st := newServerTester([]string{"--no-via"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "foo"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+               w.Header().Add("Via", "bar")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1NoVia",
+               header: []hpack.HeaderField{
+                       pair("via", "foo"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "bar"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1HostRewrite tests that server rewrites host header field
+func TestH2H1HostRewrite(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1HostRewrite",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := res.header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1NoHostRewrite tests that server does not rewrite host
+// header field
+func TestH2H1NoHostRewrite(t *testing.T) {
+       st := newServerTester([]string{"--no-host-rewrite"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1NoHostRewrite",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := res.header.Get("request-host"), st.frontendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1BadRequestCL tests that server rejects request whose
+// content-length header field value does not match its request body
+// size.
+func TestH2H1BadRequestCL(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       // we set content-length: 1024, but the actual request body is
+       // 3 bytes.
+       res, err := st.http2(requestParam{
+               name:   "TestH2H1BadRequestCL",
+               method: "POST",
+               header: []hpack.HeaderField{
+                       pair("content-length", "1024"),
+               },
+               body: []byte("foo"),
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+
+       want := http2.ErrCodeProtocol
+       if res.errCode != want {
+               t.Errorf("res.errCode = %v; want %v", res.errCode, want)
+       }
+}
+
+// TestH2H1BadResponseCL tests that server returns error when
+// content-length response header field value does not match its
+// response body size.
+func TestH2H1BadResponseCL(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               // we set content-length: 1024, but only send 3 bytes.
+               w.Header().Add("Content-Length", "1024")
+               w.Write([]byte("foo"))
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1BadResponseCL",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+
+       want := http2.ErrCodeProtocol
+       if res.errCode != want {
+               t.Errorf("res.errCode = %v; want %v", res.errCode, want)
+       }
+}
+
+// TestH2H1LocationRewrite tests location header field rewriting
+// works.
+func TestH2H1LocationRewrite(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               // TODO we cannot get st.ts's port number here.. 8443
+               // is just a place holder.  We ignore it on rewrite.
+               w.Header().Add("Location", "http://127.0.0.1:8443/p/q?a=b#fragment")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1LocationRewrite",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+
+       want := fmt.Sprintf("http://127.0.0.1:%v/p/q?a=b#fragment", serverPort)
+       if got := res.header.Get("Location"); got != want {
+               t.Errorf("Location: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1ChunkedRequestBody tests that chunked request body works.
+func TestH2H1ChunkedRequestBody(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               want := "[chunked]"
+               if got := fmt.Sprint(r.TransferEncoding); got != want {
+                       t.Errorf("Transfer-Encoding: %v; want %v", got, want)
+               }
+               body, err := ioutil.ReadAll(r.Body)
+               if err != nil {
+                       t.Fatalf("Error reading r.body: %v", err)
+               }
+               want = "foo"
+               if got := string(body); got != want {
+                       t.Errorf("body: %v; want %v", got, want)
+               }
+       })
+       defer st.Close()
+
+       _, err := st.http2(requestParam{
+               name:   "TestH2H1ChunkedRequestBody",
+               method: "POST",
+               body:   []byte("foo"),
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+}
+
+// TestH2H1MultipleRequestCL tests that server rejects request with
+// multiple Content-Length request header fields.
+func TestH2H1MultipleRequestCL(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Errorf("server should not forward bad request")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1MultipleRequestCL",
+               header: []hpack.HeaderField{
+                       pair("content-length", "1"),
+                       pair("content-length", "1"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       want := 400
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1InvalidRequestCL tests that server rejects request with
+// Content-Length which cannot be parsed as a number.
+func TestH2H1InvalidRequestCL(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Errorf("server should not forward bad request")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1InvalidRequestCL",
+               header: []hpack.HeaderField{
+                       pair("content-length", ""),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       want := 400
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1ConnectFailure tests that server handles the situation that
+// connection attempt to HTTP/1 backend failed.
+func TestH2H1ConnectFailure(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       // shutdown backend server to simulate backend connect failure
+       st.ts.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1ConnectFailure",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       want := 503
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1AssembleCookies tests that crumbled cookies in HTTP/2
+// request is assembled into 1 when forwarding to HTTP/1 backend link.
+func TestH2H1AssembleCookies(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Cookie"), "alpha; bravo; charlie"; got != want {
+                       t.Errorf("Cookie: %v; want %v", got, want)
+               }
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1AssembleCookies",
+               header: []hpack.HeaderField{
+                       pair("cookie", "alpha"),
+                       pair("cookie", "bravo"),
+                       pair("cookie", "charlie"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1TETrailers tests that server accepts TE request header
+// field if it has trailers only.
+func TestH2H1TETrailers(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1TETrailers",
+               header: []hpack.HeaderField{
+                       pair("te", "trailers"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1TEGzip tests that server resets stream if TE request header
+// field contains gzip.
+func TestH2H1TEGzip(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Error("server should not forward bad request")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1TEGzip",
+               header: []hpack.HeaderField{
+                       pair("te", "gzip"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.errCode, http2.ErrCodeProtocol; got != want {
+               t.Errorf("res.errCode = %v; want %v", res.errCode, want)
+       }
+}
+
+// TestH2H1SNI tests server's TLS SNI extension feature.  It must
+// choose appropriate certificate depending on the indicated
+// server_name from client.
+func TestH2H1SNI(t *testing.T) {
+       st := newServerTesterTLSConfig([]string{"--subcert=" + testDir + "/alt-server.key:" + testDir + "/alt-server.crt"}, t, noopHandler, &tls.Config{
+               ServerName: "alt-domain",
+       })
+       defer st.Close()
+
+       tlsConn := st.conn.(*tls.Conn)
+       connState := tlsConn.ConnectionState()
+       cert := connState.PeerCertificates[0]
+
+       if got, want := cert.Subject.CommonName, "alt-domain"; got != want {
+               t.Errorf("CommonName: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1ServerPush tests server push using Link header field from
+// backend server.
+func TestH2H1ServerPush(t *testing.T) {
+       st := newServerTester(nil, t, func(w http.ResponseWriter, r *http.Request) {
+               // only resources marked as rel=preload are pushed
+               w.Header().Add("Link", "</css/main.css>; rel=preload, </foo>, </css/theme.css>; rel=preload")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H1ServerPush",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("res.status: %v; want %v", got, want)
+       }
+       if got, want := len(res.pushResponse), 2; got != want {
+               t.Fatalf("len(res.pushResponse): %v; want %v", got, want)
+       }
+       mainCSS := res.pushResponse[0]
+       if got, want := mainCSS.status, 200; got != want {
+               t.Errorf("mainCSS.status: %v; want %v", got, want)
+       }
+       themeCSS := res.pushResponse[1]
+       if got, want := themeCSS.status, 200; got != want {
+               t.Errorf("themeCSS.status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H1GracefulShutdown tests graceful shutdown.
+func TestH2H1GracefulShutdown(t *testing.T) {
+       st := newServerTester(nil, t, noopHandler)
+       defer st.Close()
+
+       fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
+       if err := st.fr.WriteSettings(); err != nil {
+               t.Fatalf("st.fr.WriteSettings(): %v", err)
+       }
+
+       header := []hpack.HeaderField{
+               pair(":method", "GET"),
+               pair(":scheme", "http"),
+               pair(":authority", st.authority),
+               pair(":path", "/"),
+       }
+
+       for _, h := range header {
+               _ = st.enc.WriteField(h)
+       }
+
+       if err := st.fr.WriteHeaders(http2.HeadersFrameParam{
+               StreamID:      1,
+               EndStream:     false,
+               EndHeaders:    true,
+               BlockFragment: st.headerBlkBuf.Bytes(),
+       }); err != nil {
+               t.Fatalf("st.fr.WriteHeaders(): %v", err)
+       }
+
+       // send SIGQUIT signal to nghttpx to perform graceful shutdown
+       st.cmd.Process.Signal(syscall.SIGQUIT)
+
+       // after signal, finish request body
+       if err := st.fr.WriteData(1, true, nil); err != nil {
+               t.Fatalf("st.fr.WriteData(): %v", err)
+       }
+
+       numGoAway := 0
+
+       for {
+               fr, err := st.readFrame()
+               if err != nil {
+                       if err == io.EOF {
+                               want := 2
+                               if got := numGoAway; got != want {
+                                       t.Fatalf("numGoAway: %v; want %v", got, want)
+                               }
+                               return
+                       }
+                       t.Fatalf("st.readFrame(): %v", err)
+               }
+               switch f := fr.(type) {
+               case *http2.GoAwayFrame:
+                       numGoAway += 1
+                       want := http2.ErrCodeNo
+                       if got := f.ErrCode; got != want {
+                               t.Fatalf("f.ErrCode(%v): %v; want %v", numGoAway, got, want)
+                       }
+                       switch numGoAway {
+                       case 1:
+                               want := (uint32(1) << 31) - 1
+                               if got := f.LastStreamID; got != want {
+                                       t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want)
+                               }
+                       case 2:
+                               want := uint32(1)
+                               if got := f.LastStreamID; got != want {
+                                       t.Fatalf("f.LastStreamID(%v): %v; want %v", numGoAway, got, want)
+                               }
+                       case 3:
+                               t.Fatalf("too many GOAWAYs received")
+                       }
+               }
+       }
+}
+
+// TestH2H2MultipleResponseCL tests that server returns error if
+// multiple Content-Length response header fields are received.
+func TestH2H2MultipleResponseCL(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("content-length", "1")
+               w.Header().Add("content-length", "1")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H2MultipleResponseCL",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       want := 502
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H2InvalidResponseCL tests that server returns error if
+// Content-Length response header field value cannot be parsed as a
+// number.
+func TestH2H2InvalidResponseCL(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("content-length", "")
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H2InvalidResponseCL",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       want := 502
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H2ConnectFailure tests that server handles the situation that
+// connection attempt to HTTP/2 backend failed.
+func TestH2H2ConnectFailure(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, noopHandler)
+       defer st.Close()
+
+       // simulate backend connect attempt failure
+       st.ts.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H2ConnectFailure",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       want := 503
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestH2H2HostRewrite tests that server rewrites host header field
+func TestH2H2HostRewrite(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H2HostRewrite",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := res.header.Get("request-host"), st.backendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
+
+// TestH2H2NoHostRewrite tests that server does not rewrite host
+// header field
+func TestH2H2NoHostRewrite(t *testing.T) {
+       st := newServerTester([]string{"--http2-bridge", "--no-host-rewrite"}, t, func(w http.ResponseWriter, r *http.Request) {
+               w.Header().Add("request-host", r.Host)
+       })
+       defer st.Close()
+
+       res, err := st.http2(requestParam{
+               name: "TestH2H2NoHostRewrite",
+       })
+       if err != nil {
+               t.Fatalf("Error st.http2() = %v", err)
+       }
+       if got, want := res.status, 200; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+       if got, want := res.header.Get("request-host"), st.frontendHost; got != want {
+               t.Errorf("request-host: %v; want %v", got, want)
+       }
+}
diff --git a/integration-tests/nghttpx_spdy_test.go b/integration-tests/nghttpx_spdy_test.go
new file mode 100644 (file)
index 0000000..0b28939
--- /dev/null
@@ -0,0 +1,192 @@
+package nghttp2
+
+import (
+       "github.com/bradfitz/http2/hpack"
+       "golang.org/x/net/spdy"
+       "net/http"
+       "testing"
+)
+
+// TestS3H1PlainGET tests whether simple SPDY GET request works.
+func TestS3H1PlainGET(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler)
+       defer st.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H1PlainGET",
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+
+       want := 200
+       if got := res.status; got != want {
+               t.Errorf("status = %v; want %v", got, want)
+       }
+}
+
+// TestS3H1BadRequestCL tests that server rejects request whose
+// content-length header field value does not match its request body
+// size.
+func TestS3H1BadRequestCL(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, noopHandler)
+       defer st.Close()
+
+       // we set content-length: 1024, but the actual request body is
+       // 3 bytes.
+       res, err := st.spdy(requestParam{
+               name:   "TestS3H1BadRequestCL",
+               method: "POST",
+               header: []hpack.HeaderField{
+                       pair("content-length", "1024"),
+               },
+               body: []byte("foo"),
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+
+       want := spdy.ProtocolError
+       if got := res.spdyRstErrCode; got != want {
+               t.Errorf("res.spdyRstErrCode = %v; want %v", got, want)
+       }
+}
+
+// TestS3H1MultipleRequestCL tests that server rejects request with
+// multiple Content-Length request header fields.
+func TestS3H1MultipleRequestCL(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Errorf("server should not forward bad request")
+       })
+       defer st.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H1MultipleRequestCL",
+               header: []hpack.HeaderField{
+                       pair("content-length", "1"),
+                       pair("content-length", "1"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+       want := 400
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestS3H1InvalidRequestCL tests that server rejects request with
+// Content-Length which cannot be parsed as a number.
+func TestS3H1InvalidRequestCL(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
+               t.Errorf("server should not forward bad request")
+       })
+       defer st.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H1InvalidRequestCL",
+               header: []hpack.HeaderField{
+                       pair("content-length", ""),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+       want := 400
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
+
+// TestS3H1GenerateVia tests that server generates Via header field to and
+// from backend server.
+func TestS3H1GenerateVia(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "1.1 nghttpx"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+       })
+       defer st.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H1GenerateVia",
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "1.1 nghttpx"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestS3H1AppendVia tests that server adds value to existing Via
+// header field to and from backend server.
+func TestS3H1AppendVia(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "foo, 1.1 nghttpx"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+               w.Header().Add("Via", "bar")
+       })
+       defer st.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H1AppendVia",
+               header: []hpack.HeaderField{
+                       pair("via", "foo"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "bar, 1.1 nghttpx"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestS3H1NoVia tests that server does not add value to existing Via
+// header field to and from backend server.
+func TestS3H1NoVia(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--no-via"}, t, func(w http.ResponseWriter, r *http.Request) {
+               if got, want := r.Header.Get("Via"), "foo"; got != want {
+                       t.Errorf("Via: %v; want %v", got, want)
+               }
+               w.Header().Add("Via", "bar")
+       })
+       defer st.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H1NoVia",
+               header: []hpack.HeaderField{
+                       pair("via", "foo"),
+               },
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+       if got, want := res.header.Get("Via"), "bar"; got != want {
+               t.Errorf("Via: %v; want %v", got, want)
+       }
+}
+
+// TestS3H2ConnectFailure tests that server handles the situation that
+// connection attempt to HTTP/2 backend failed.
+func TestS3H2ConnectFailure(t *testing.T) {
+       st := newServerTesterTLS([]string{"--npn-list=spdy/3.1", "--http2-bridge"}, t, noopHandler)
+       defer st.Close()
+
+       // simulate backend connect attempt failure
+       st.ts.Close()
+
+       res, err := st.spdy(requestParam{
+               name: "TestS3H2ConnectFailure",
+       })
+       if err != nil {
+               t.Fatalf("Error st.spdy() = %v", err)
+       }
+       want := 503
+       if got := res.status; got != want {
+               t.Errorf("status: %v; want %v", got, want)
+       }
+}
diff --git a/integration-tests/server.crt b/integration-tests/server.crt
new file mode 100644 (file)
index 0000000..c50fdaa
--- /dev/null
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhTCCAm2gAwIBAgIJAOvIx8xIxgyOMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCTEyNy4wLjAuMTAeFw0xNTAxMjMxMjI0
+MjdaFw0yNTAxMjAxMjI0MjdaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l
+LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
+BAMMCTEyNy4wLjAuMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMuI
+QZRI/iBaxPTjTWGemt8tCEfzZWxuIW3hY/gIhwJDfH2SbourBh1s9vqcqhBq5vmo
+kdfVQXAnNLjIG1uhWmcHuNnKrE5hU82N6i9RsmuM5TQRvhsamHri4G+EXJMu9GqF
+Mso8g7MWpRSGKf+8gfjAVNwfCHFiu8oBcMmy3l54MFHgRLSveAMhiPB0e3Xlnpr5
+2bS/oGTx5ynwPgBpEn2FrpT4Z/aLCLzJ/ysgNH8BXEh7n/v7xM3vd5grqB039rd5
+JoxlWvp+4XpzKp5upaqmOcVUq4pDSFUQ3w6C+v33Z3OK6Qaon7GMxLv3Us3b7PZ3
+1CLoWJR2o3OSnUfO/gUCAwEAAaNQME4wHQYDVR0OBBYEFLc5JWPUUVx4GJesogMV
+w2Rz0L3yMB8GA1UdIwQYMBaAFLc5JWPUUVx4GJesogMVw2Rz0L3yMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAP/cJWpM+GEjmVYHFacKTdbXBMox2Xn
+QY2NLm00WPOGvKnO7czMFfX/pEmiq71kD45rLLfbaJP205QpxqiAIvhFhuq50Co7
+sTDtwcDTPLX9H7Ugjt4sTMPiwC14uVXFfoT/J46zMjXwP00qKyfszc2tkIgHfrTl
+h4M1hkdfmMximir/Ii7TdYYJ3oGS8tdcYb6D4DZwAljKmxF6iUOwFCUgpTmqDBT5
+irXY8D27DzuNN5Pg07rwAlwXLCzrJE10UtO4MmRVXwpzmoaRQD4/tna6bZzdetvs
+gPdGP6W1o0q85gullieMJWeKyQA/wasoE7fypn4pHAdTZm/vH+v7GHg=
+-----END CERTIFICATE-----
diff --git a/integration-tests/server.key b/integration-tests/server.key
new file mode 100644 (file)
index 0000000..0fc02bb
--- /dev/null
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLiEGUSP4gWsT0
+401hnprfLQhH82VsbiFt4WP4CIcCQ3x9km6LqwYdbPb6nKoQaub5qJHX1UFwJzS4
+yBtboVpnB7jZyqxOYVPNjeovUbJrjOU0Eb4bGph64uBvhFyTLvRqhTLKPIOzFqUU
+hin/vIH4wFTcHwhxYrvKAXDJst5eeDBR4ES0r3gDIYjwdHt15Z6a+dm0v6Bk8ecp
+8D4AaRJ9ha6U+Gf2iwi8yf8rIDR/AVxIe5/7+8TN73eYK6gdN/a3eSaMZVr6fuF6
+cyqebqWqpjnFVKuKQ0hVEN8Ogvr992dziukGqJ+xjMS791LN2+z2d9Qi6FiUdqNz
+kp1Hzv4FAgMBAAECggEACG26GYP0Ui6wHVwUZkiFLVzWDPS9bIIbDEvbMfhYbvWQ
+gDrCLTKF7E4I5FP8jvV+XzRl5cRFE3nsKwLObzr9XWrqcsp73DsXl1mbKx58/ws0
+qrVZZBHz4pLmrHeUxduZ75dYhRuAcLgtWe48awTJdR2x5fO7C8cE89afbxrjLpJE
+tVyiw6vVB0GfWTZodxtAFMTX1KVm4bTngXfg0NF1FBNHAX3Cm6t4YCE41hKSc0IQ
+Jr3C4e9uj8poze1B17k79bGB8HNMbbc8Ws0sdbxi5xnY+HUA/mYQrmGXo8sdqiYC
+EYCMqPm3iJrCmmpHukGf2Vt9k1aLlJ+lxOclSwFO+QKBgQDoRmoprfdmU20LyxYH
+eVeVqggqmhNohwnuhIvOAyrWGUkbDsssqx2Vv82z0WHAAkwEvQ984UzaYWCCL3m3
++JzpF2dz6aKhXIaYnXBlk3STMGUCDT5ysPvsin9z/unzkffh3vrbDBARGFYWG18x
+eUyTDOVVeTZNHUJXGjRyiftCkwKBgQDgUkR6dHU4ciSt7Y0UkyAgtZ7POR41T05L
+bcxbjJeqm6qlj+oP9WUk7JxeSEFUbrMiROABLPPqTwmGo4xrDRx/e7WrqN6QBKC+
+Y8CfalrKRb0np60x7Mxx0kbmHp5cwv9QDKznKViOYSgKxFrOFZyMAEXQdZ3FvjXF
+OQWrw86kBwKBgQDXuxa9MWO3uUJtkqkaNfw/+FVvY/0kt09lJdxHci+l/IQmyl2w
+Vhm7TRK7sXvtfvSl7gblgMgFiC2/nGKbmR/7ag5e3R98aVhlhMywuvyp/GfEORLI
+KVNChfwMezVFUUx+j8BEFHcTuZuzGqcWZ0fUyER0V4k0pDlKdv9BZqBkWwKBgCdP
+o3qGQCilMDJex/OMGPxCd9M+4kFbZZAobMC6cbXPU+dxwgYL7i67XGfVZ8WBJNlj
+kpICK7irIzM6JBh6krzwlBTCIkbA2N6kopQNUl3SPOTfKKXwJp/nxs77HKuK7K09
+m2tjPoatFhRU9sjY1rdeMN3oTr7hp5CpfonsZaEvAoGAEPsZcDd4N9ap5bgaeDy9
+NOfLsIyaxT5k6moRIiy83QPihvCuECP16+r6M5tiSfgt/PtCimdjhRiqXzIHNRhh
+Nfsv13vUtZgt8cYXuTdI4a8feKI7Q4876ME8Qp3WM5/UNZWq6/sWCuZFqbXUhqM0
+mwNEi5Zddzf8VsSL2gCraQg=
+-----END PRIVATE KEY-----
diff --git a/integration-tests/server_tester.go b/integration-tests/server_tester.go
new file mode 100644 (file)
index 0000000..b8a5030
--- /dev/null
@@ -0,0 +1,616 @@
+package nghttp2
+
+import (
+       "bufio"
+       "bytes"
+       "crypto/tls"
+       "errors"
+       "fmt"
+       "github.com/bradfitz/http2"
+       "github.com/bradfitz/http2/hpack"
+       "github.com/tatsuhiro-t/go-nghttp2"
+       "golang.org/x/net/spdy"
+       "io"
+       "io/ioutil"
+       "net"
+       "net/http"
+       "net/http/httptest"
+       "net/url"
+       "os/exec"
+       "sort"
+       "strconv"
+       "strings"
+       "testing"
+       "time"
+)
+
+const (
+       serverBin  = buildDir + "/src/nghttpx"
+       serverPort = 3009
+       testDir    = buildDir + "/integration-tests"
+)
+
+func pair(name, value string) hpack.HeaderField {
+       return hpack.HeaderField{
+               Name:  name,
+               Value: value,
+       }
+}
+
+type serverTester struct {
+       args          []string  // command-line arguments
+       cmd           *exec.Cmd // test frontend server process, which is test subject
+       url           string    // test frontend server URL
+       t             *testing.T
+       ts            *httptest.Server // backend server
+       frontendHost  string           // frontend server host
+       backendHost   string           // backend server host
+       conn          net.Conn         // connection to frontend server
+       h2PrefaceSent bool             // HTTP/2 preface was sent in conn
+       nextStreamID  uint32           // next stream ID
+       fr            *http2.Framer    // HTTP/2 framer
+       spdyFr        *spdy.Framer     // SPDY/3.1 framer
+       headerBlkBuf  bytes.Buffer     // buffer to store encoded header block
+       enc           *hpack.Encoder   // HTTP/2 HPACK encoder
+       header        http.Header      // received header fields
+       dec           *hpack.Decoder   // HTTP/2 HPACK decoder
+       authority     string           // server's host:port
+       frCh          chan http2.Frame // used for incoming HTTP/2 frame
+       spdyFrCh      chan spdy.Frame  // used for incoming SPDY frame
+       errCh         chan error
+}
+
+// newServerTester creates test context for plain TCP frontend
+// connection.
+func newServerTester(args []string, t *testing.T, handler http.HandlerFunc) *serverTester {
+       return newServerTesterInternal(args, t, handler, false, nil)
+}
+
+// newServerTester creates test context for TLS frontend connection.
+func newServerTesterTLS(args []string, t *testing.T, handler http.HandlerFunc) *serverTester {
+       return newServerTesterInternal(args, t, handler, true, nil)
+}
+
+// newServerTester creates test context for TLS frontend connection
+// with given clientConfig
+func newServerTesterTLSConfig(args []string, t *testing.T, handler http.HandlerFunc, clientConfig *tls.Config) *serverTester {
+       return newServerTesterInternal(args, t, handler, true, clientConfig)
+}
+
+// newServerTesterInternal creates test context.  If frontendTLS is
+// true, set up TLS frontend connection.
+func newServerTesterInternal(args []string, t *testing.T, handler http.HandlerFunc, frontendTLS bool, clientConfig *tls.Config) *serverTester {
+       ts := httptest.NewUnstartedServer(handler)
+
+       backendTLS := false
+       for _, k := range args {
+               switch k {
+               case "--http2-bridge":
+                       backendTLS = true
+               }
+       }
+       if backendTLS {
+               nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{})
+               // According to httptest/server.go, we have to set
+               // NextProtos separately for ts.TLS.  NextProtos set
+               // in nghttp2.ConfigureServer is effectively ignored.
+               ts.TLS = new(tls.Config)
+               ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2-14")
+               ts.StartTLS()
+               args = append(args, "-k")
+       } else {
+               ts.Start()
+       }
+       scheme := "http"
+       if frontendTLS {
+               scheme = "https"
+               args = append(args, testDir+"/server.key", testDir+"/server.crt")
+       } else {
+               args = append(args, "--frontend-no-tls")
+       }
+
+       backendURL, err := url.Parse(ts.URL)
+       if err != nil {
+               t.Fatalf("Error parsing URL from httptest.Server: %v", err)
+       }
+
+       // URL.Host looks like "127.0.0.1:8080", but we want
+       // "127.0.0.1,8080"
+       b := "-b" + strings.Replace(backendURL.Host, ":", ",", -1)
+       args = append(args, fmt.Sprintf("-f127.0.0.1,%v", serverPort), b,
+               "--errorlog-file="+testDir+"/log.txt", "-LINFO")
+
+       authority := fmt.Sprintf("127.0.0.1:%v", serverPort)
+
+       st := &serverTester{
+               cmd:          exec.Command(serverBin, args...),
+               t:            t,
+               ts:           ts,
+               url:          fmt.Sprintf("%v://%v", scheme, authority),
+               frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort),
+               backendHost:  backendURL.Host,
+               nextStreamID: 1,
+               authority:    authority,
+               frCh:         make(chan http2.Frame),
+               spdyFrCh:     make(chan spdy.Frame),
+               errCh:        make(chan error),
+       }
+
+       if err := st.cmd.Start(); err != nil {
+               st.t.Fatalf("Error starting %v: %v", serverBin, err)
+       }
+
+       retry := 0
+       for {
+               var conn net.Conn
+               var err error
+               if frontendTLS {
+                       var tlsConfig *tls.Config
+                       if clientConfig == nil {
+                               tlsConfig = new(tls.Config)
+                       } else {
+                               tlsConfig = clientConfig
+                       }
+                       tlsConfig.InsecureSkipVerify = true
+                       tlsConfig.NextProtos = []string{"h2-14", "spdy/3.1"}
+                       conn, err = tls.Dial("tcp", authority, tlsConfig)
+               } else {
+                       conn, err = net.Dial("tcp", authority)
+               }
+               if err != nil {
+                       retry += 1
+                       if retry >= 100 {
+                               st.Close()
+                               st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid")
+                       }
+                       time.Sleep(150 * time.Millisecond)
+                       continue
+               }
+               if frontendTLS {
+                       tlsConn := conn.(*tls.Conn)
+                       cs := tlsConn.ConnectionState()
+                       if !cs.NegotiatedProtocolIsMutual {
+                               st.Close()
+                               st.t.Fatalf("Error negotiated next protocol is not mutual")
+                       }
+               }
+               st.conn = conn
+               break
+       }
+
+       st.fr = http2.NewFramer(st.conn, st.conn)
+       spdyFr, err := spdy.NewFramer(st.conn, st.conn)
+       if err != nil {
+               st.Close()
+               st.t.Fatalf("Error spdy.NewFramer: %v", err)
+       }
+       st.spdyFr = spdyFr
+       st.enc = hpack.NewEncoder(&st.headerBlkBuf)
+       st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) {
+               st.header.Add(f.Name, f.Value)
+       })
+
+       return st
+}
+
+func (st *serverTester) Close() {
+       if st.conn != nil {
+               st.conn.Close()
+       }
+       if st.cmd != nil {
+               st.cmd.Process.Kill()
+               st.cmd.Wait()
+       }
+       if st.ts != nil {
+               st.ts.Close()
+       }
+}
+
+func (st *serverTester) readFrame() (http2.Frame, error) {
+       go func() {
+               f, err := st.fr.ReadFrame()
+               if err != nil {
+                       st.errCh <- err
+                       return
+               }
+               st.frCh <- f
+       }()
+
+       select {
+       case f := <-st.frCh:
+               return f, nil
+       case err := <-st.errCh:
+               return nil, err
+       case <-time.After(5 * time.Second):
+               return nil, errors.New("timeout waiting for frame")
+       }
+}
+
+func (st *serverTester) readSpdyFrame() (spdy.Frame, error) {
+       go func() {
+               f, err := st.spdyFr.ReadFrame()
+               if err != nil {
+                       st.errCh <- err
+                       return
+               }
+               st.spdyFrCh <- f
+       }()
+
+       select {
+       case f := <-st.spdyFrCh:
+               return f, nil
+       case err := <-st.errCh:
+               return nil, err
+       case <-time.After(2 * time.Second):
+               return nil, errors.New("timeout waiting for frame")
+       }
+}
+
+type requestParam struct {
+       name      string              // name for this request to identify the request in log easily
+       streamID  uint32              // stream ID, automatically assigned if 0
+       method    string              // method, defaults to GET
+       scheme    string              // scheme, defaults to http
+       authority string              // authority, defaults to backend server address
+       path      string              // path, defaults to /
+       header    []hpack.HeaderField // additional request header fields
+       body      []byte              // request body
+}
+
+func (st *serverTester) http1(rp requestParam) (*serverResponse, error) {
+       method := "GET"
+       if rp.method != "" {
+               method = rp.method
+       }
+
+       var body io.Reader
+       if rp.body != nil {
+               body = bytes.NewBuffer(rp.body)
+       }
+       req, err := http.NewRequest(method, st.url, body)
+       if err != nil {
+               return nil, err
+       }
+       for _, h := range rp.header {
+               req.Header.Add(h.Name, h.Value)
+       }
+       req.Header.Add("Test-Case", rp.name)
+
+       if err := req.Write(st.conn); err != nil {
+               return nil, err
+       }
+       resp, err := http.ReadResponse(bufio.NewReader(st.conn), req)
+       if err != nil {
+               return nil, err
+       }
+       respBody, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return nil, err
+       }
+       resp.Body.Close()
+
+       res := &serverResponse{
+               status:    resp.StatusCode,
+               header:    resp.Header,
+               body:      respBody,
+               connClose: resp.Close,
+       }
+
+       return res, nil
+}
+
+func (st *serverTester) spdy(rp requestParam) (*serverResponse, error) {
+       res := &serverResponse{}
+
+       var id spdy.StreamId
+       if rp.streamID != 0 {
+               id = spdy.StreamId(rp.streamID)
+               if id >= spdy.StreamId(st.nextStreamID) && id%2 == 1 {
+                       st.nextStreamID = uint32(id) + 2
+               }
+       } else {
+               id = spdy.StreamId(st.nextStreamID)
+               st.nextStreamID += 2
+       }
+
+       method := "GET"
+       if rp.method != "" {
+               method = rp.method
+       }
+
+       scheme := "http"
+       if rp.scheme != "" {
+               scheme = rp.scheme
+       }
+
+       host := st.authority
+       if rp.authority != "" {
+               host = rp.authority
+       }
+
+       path := "/"
+       if rp.path != "" {
+               path = rp.path
+       }
+
+       header := make(http.Header)
+       header.Add(":method", method)
+       header.Add(":scheme", scheme)
+       header.Add(":host", host)
+       header.Add(":path", path)
+       header.Add(":version", "HTTP/1.1")
+       header.Add("test-case", rp.name)
+       for _, h := range rp.header {
+               header.Add(h.Name, h.Value)
+       }
+
+       var synStreamFlags spdy.ControlFlags
+       if len(rp.body) == 0 {
+               synStreamFlags = spdy.ControlFlagFin
+       }
+       if err := st.spdyFr.WriteFrame(&spdy.SynStreamFrame{
+               CFHeader: spdy.ControlFrameHeader{
+                       Flags: synStreamFlags,
+               },
+               StreamId: id,
+               Headers:  header,
+       }); err != nil {
+               return nil, err
+       }
+
+       if len(rp.body) != 0 {
+               if err := st.spdyFr.WriteFrame(&spdy.DataFrame{
+                       StreamId: id,
+                       Flags:    spdy.DataFlagFin,
+                       Data:     rp.body,
+               }); err != nil {
+                       return nil, err
+               }
+       }
+
+loop:
+       for {
+               fr, err := st.readSpdyFrame()
+               if err != nil {
+                       return res, err
+               }
+               switch f := fr.(type) {
+               case *spdy.SynReplyFrame:
+                       if f.StreamId != id {
+                               break
+                       }
+                       res.header = cloneHeader(f.Headers)
+                       if _, err := fmt.Sscan(res.header.Get(":status"), &res.status); err != nil {
+                               return res, fmt.Errorf("Error parsing status code: %v", err)
+                       }
+                       if f.CFHeader.Flags&spdy.ControlFlagFin != 0 {
+                               break loop
+                       }
+               case *spdy.DataFrame:
+                       if f.StreamId != id {
+                               break
+                       }
+                       res.body = append(res.body, f.Data...)
+                       if f.Flags&spdy.DataFlagFin != 0 {
+                               break loop
+                       }
+               case *spdy.RstStreamFrame:
+                       if f.StreamId != id {
+                               break
+                       }
+                       res.spdyRstErrCode = f.Status
+                       break loop
+               case *spdy.GoAwayFrame:
+                       if f.Status == spdy.GoAwayOK {
+                               break
+                       }
+                       res.spdyGoAwayErrCode = f.Status
+                       break loop
+               }
+       }
+       return res, nil
+}
+
+func (st *serverTester) http2(rp requestParam) (*serverResponse, error) {
+       st.headerBlkBuf.Reset()
+       st.header = make(http.Header)
+
+       var id uint32
+       if rp.streamID != 0 {
+               id = rp.streamID
+               if id >= st.nextStreamID && id%2 == 1 {
+                       st.nextStreamID = id + 2
+               }
+       } else {
+               id = st.nextStreamID
+               st.nextStreamID += 2
+       }
+
+       if !st.h2PrefaceSent {
+               st.h2PrefaceSent = true
+               fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n")
+               if err := st.fr.WriteSettings(); err != nil {
+                       return nil, err
+               }
+       }
+
+       res := &serverResponse{
+               streamID: id,
+       }
+
+       streams := make(map[uint32]*serverResponse)
+       streams[id] = res
+
+       method := "GET"
+       if rp.method != "" {
+               method = rp.method
+       }
+       _ = st.enc.WriteField(pair(":method", method))
+
+       scheme := "http"
+       if rp.scheme != "" {
+               scheme = rp.scheme
+       }
+       _ = st.enc.WriteField(pair(":scheme", scheme))
+
+       authority := st.authority
+       if rp.authority != "" {
+               authority = rp.authority
+       }
+       _ = st.enc.WriteField(pair(":authority", authority))
+
+       path := "/"
+       if rp.path != "" {
+               path = rp.path
+       }
+       _ = st.enc.WriteField(pair(":path", path))
+
+       _ = st.enc.WriteField(pair("test-case", rp.name))
+
+       for _, h := range rp.header {
+               _ = st.enc.WriteField(h)
+       }
+
+       err := st.fr.WriteHeaders(http2.HeadersFrameParam{
+               StreamID:      id,
+               EndStream:     len(rp.body) == 0,
+               EndHeaders:    true,
+               BlockFragment: st.headerBlkBuf.Bytes(),
+       })
+       if err != nil {
+               return nil, err
+       }
+
+       if len(rp.body) != 0 {
+               // TODO we assume rp.body fits in 1 frame
+               if err := st.fr.WriteData(id, true, rp.body); err != nil {
+                       return nil, err
+               }
+       }
+
+loop:
+       for {
+               fr, err := st.readFrame()
+               if err != nil {
+                       return res, err
+               }
+               switch f := fr.(type) {
+               case *http2.HeadersFrame:
+                       _, err := st.dec.Write(f.HeaderBlockFragment())
+                       if err != nil {
+                               return res, err
+                       }
+                       sr, ok := streams[f.FrameHeader.StreamID]
+                       if !ok {
+                               st.header = make(http.Header)
+                               break
+                       }
+                       sr.header = cloneHeader(st.header)
+                       var status int
+                       status, err = strconv.Atoi(sr.header.Get(":status"))
+                       if err != nil {
+                               return res, fmt.Errorf("Error parsing status code: %v", err)
+                       }
+                       sr.status = status
+                       if f.StreamEnded() {
+                               if streamEnded(res, streams, sr) {
+                                       break loop
+                               }
+                       }
+               case *http2.PushPromiseFrame:
+                       _, err := st.dec.Write(f.HeaderBlockFragment())
+                       if err != nil {
+                               return res, err
+                       }
+                       sr := &serverResponse{
+                               streamID:  f.PromiseID,
+                               reqHeader: cloneHeader(st.header),
+                       }
+                       streams[sr.streamID] = sr
+               case *http2.DataFrame:
+                       sr, ok := streams[f.FrameHeader.StreamID]
+                       if !ok {
+                               break
+                       }
+                       sr.body = append(sr.body, f.Data()...)
+                       if f.StreamEnded() {
+                               if streamEnded(res, streams, sr) {
+                                       break loop
+                               }
+                       }
+               case *http2.RSTStreamFrame:
+                       sr, ok := streams[f.FrameHeader.StreamID]
+                       if !ok {
+                               break
+                       }
+                       sr.errCode = f.ErrCode
+                       if streamEnded(res, streams, sr) {
+                               break loop
+                       }
+               case *http2.GoAwayFrame:
+                       if f.ErrCode == http2.ErrCodeNo {
+                               break
+                       }
+                       res.errCode = f.ErrCode
+                       res.connErr = true
+                       break loop
+               case *http2.SettingsFrame:
+                       if f.IsAck() {
+                               break
+                       }
+                       if err := st.fr.WriteSettingsAck(); err != nil {
+                               return res, err
+                       }
+               }
+       }
+       sort.Sort(ByStreamID(res.pushResponse))
+       return res, nil
+}
+
+func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool {
+       delete(streams, sr.streamID)
+       if mainSr.streamID != sr.streamID {
+               mainSr.pushResponse = append(mainSr.pushResponse, sr)
+       }
+       return len(streams) == 0
+}
+
+type serverResponse struct {
+       status            int                  // HTTP status code
+       header            http.Header          // response header fields
+       body              []byte               // response body
+       streamID          uint32               // stream ID in HTTP/2
+       errCode           http2.ErrCode        // error code received in HTTP/2 RST_STREAM or GOAWAY
+       connErr           bool                 // true if HTTP/2 connection error
+       spdyGoAwayErrCode spdy.GoAwayStatus    // status code received in SPDY RST_STREAM
+       spdyRstErrCode    spdy.RstStreamStatus // status code received in SPDY GOAWAY
+       connClose         bool                 // Conection: close is included in response header in HTTP/1 test
+       reqHeader         http.Header          // http request header, currently only sotres pushed request header
+       pushResponse      []*serverResponse    // pushed response
+}
+
+type ByStreamID []*serverResponse
+
+func (b ByStreamID) Len() int {
+       return len(b)
+}
+
+func (b ByStreamID) Swap(i, j int) {
+       b[i], b[j] = b[j], b[i]
+}
+
+func (b ByStreamID) Less(i, j int) bool {
+       return b[i].streamID < b[j].streamID
+}
+
+func cloneHeader(h http.Header) http.Header {
+       h2 := make(http.Header, len(h))
+       for k, vv := range h {
+               vv2 := make([]string, len(vv))
+               copy(vv2, vv)
+               h2[k] = vv2
+       }
+       return h2
+}
+
+func noopHandler(w http.ResponseWriter, r *http.Request) {}
diff --git a/integration-tests/setenv.in b/integration-tests/setenv.in
new file mode 100644 (file)
index 0000000..2c2d3c9
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh -e
+
+export CGO_CFLAGS="-I@abs_top_srcdir@/lib/includes -I@abs_top_builddir@/lib/includes"
+export CGO_LDFLAGS="-L@abs_top_builddir@/lib/.libs"
+export LD_LIBRARY_PATH="@abs_top_builddir@/lib/.libs"
+"$@"
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644 (file)
index 0000000..35a0595
--- /dev/null
@@ -0,0 +1,65 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS = includes
+
+EXTRA_DIST = Makefile.msvc
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CPPFLAGS = -I$(srcdir)/includes -I$(builddir)/includes @DEFS@
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libnghttp2.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libnghttp2.la
+
+OBJECTS = nghttp2_pq.c nghttp2_map.c nghttp2_queue.c \
+       nghttp2_frame.c \
+       nghttp2_buf.c \
+       nghttp2_stream.c nghttp2_outbound_item.c \
+       nghttp2_session.c nghttp2_submit.c \
+       nghttp2_helper.c \
+       nghttp2_npn.c \
+       nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c \
+       nghttp2_version.c \
+       nghttp2_priority_spec.c \
+       nghttp2_option.c \
+       nghttp2_callbacks.c \
+       nghttp2_mem.c
+
+HFILES = nghttp2_pq.h nghttp2_int.h nghttp2_map.h nghttp2_queue.h \
+       nghttp2_frame.h \
+       nghttp2_buf.h \
+       nghttp2_session.h nghttp2_helper.h nghttp2_stream.h nghttp2_int.h \
+       nghttp2_npn.h \
+       nghttp2_submit.h nghttp2_outbound_item.h \
+       nghttp2_net.h \
+       nghttp2_hd.h nghttp2_hd_huffman.h \
+       nghttp2_priority_spec.h \
+       nghttp2_option.h \
+       nghttp2_callbacks.h \
+       nghttp2_mem.h
+
+libnghttp2_la_SOURCES = $(HFILES) $(OBJECTS)
+libnghttp2_la_LDFLAGS = -no-undefined \
+       -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE)
diff --git a/lib/Makefile.msvc b/lib/Makefile.msvc
new file mode 100644 (file)
index 0000000..cd5a3e2
--- /dev/null
@@ -0,0 +1,266 @@
+#\r
+# GNU Makefile for nghttp2 / MSVC.\r
+#\r
+# By G. Vanem <gvanem@yahoo.no> 2013\r
+# The MIT License apply.\r
+#\r
+\r
+#\r
+# Choose your weapons:\r
+#  Set 'ZLIB_ROOT' to the root of zlib.\r
+#  Set 'USE_CYTHON=1' to build and install the 'nghttp2.pyd' Python extension.\r
+#\r
+ZLIB_ROOT  = g:/MingW32/src/Compression/zlib-1.2.8\r
+USE_CYTHON = 1\r
+\r
+_VERSION   := $(shell grep AC_INIT ../configure.ac | cut -d'[' -f3 | sed -e 's/-DEV], //g')\r
+_VERSION   := $(subst ., ,$(_VERSION))\r
+VER_MAJOR   = $(word 1,$(_VERSION))\r
+VER_MINOR   = $(word 2,$(_VERSION))\r
+VER_MICRO   = $(word 3,$(_VERSION))\r
+VERSION     = $(VER_MAJOR).$(VER_MINOR).$(VER_MICRO)\r
+VERSION_NUM = ($(VER_MAJOR) << 16) + ($(VER_MINOR) << 8) + $(VER_MICRO)\r
+\r
+GENERATED   = 'Generated by $(realpath Makefile.MSVC)'\r
+\r
+#\r
+# Where to copy nghttp2.dll + lib + headers to.\r
+# Note: 'make install' is not in default targets. Do it explicitly.\r
+#\r
+VC_ROOT     = $(realpath $(VCINSTALLDIR))\r
+INSTALL_BIN = $(VC_ROOT)/bin\r
+INSTALL_LIB = $(VC_ROOT)/lib\r
+INSTALL_HDR = $(VC_ROOT)/include\r
+\r
+#\r
+# Build for DEBUG-model and RELEASE at the same time.\r
+#\r
+TARGETS = nghttp2.lib  nghttp2.dll  nghttp2_imp.lib \\r
+          nghttp2d.lib nghttp2d.dll nghttp2d_imp.lib\r
+\r
+EXT_LIBS = $(ZLIB_ROOT)/zlib.lib ws2_32.lib\r
+\r
+OBJ_DIR = MSVC_obj\r
+\r
+NGHTTP2_PDB_R = $(OBJ_DIR)/nghttp2.pdb\r
+NGHTTP2_PDB_D = $(OBJ_DIR)/nghttp2d.pdb\r
+\r
+CC       = cl\r
+CFLAGS   = -I./includes -I$(ZLIB_ROOT) -DHAVE_WINSOCK2_H -Dssize_t=long\r
+\r
+CFLAGS_R = -nologo -MD  -W3 -Zi -Fd./$(NGHTTP2_PDB_R)\r
+CFLAGS_D = -nologo -MDd -W3 -Zi -Fd./$(NGHTTP2_PDB_D) \\r
+           -Ot -D_DEBUG -GF -RTCs -RTCu # -RTCc -GS\r
+\r
+LDFLAGS = -nologo -machine:i386 -map -debug -incremental:no # -verbose\r
+\r
+NGHTTP2_SRC = nghttp2_buf.c             \\r
+              nghttp2_callbacks.c       \\r
+              nghttp2_frame.c           \\r
+              nghttp2_helper.c          \\r
+              nghttp2_hd.c              \\r
+              nghttp2_hd_huffman.c      \\r
+              nghttp2_hd_huffman_data.c \\r
+              nghttp2_map.c             \\r
+              nghttp2_npn.c             \\r
+              nghttp2_option.c          \\r
+              nghttp2_outbound_item.c   \\r
+              nghttp2_priority_spec.c   \\r
+              nghttp2_pq.c              \\r
+              nghttp2_queue.c           \\r
+              nghttp2_session.c         \\r
+              nghttp2_stream.c          \\r
+              nghttp2_submit.c          \\r
+              nghttp2_version.c\r
+\r
+NGHTTP2_OBJ_R = $(addprefix $(OBJ_DIR)/r_, $(notdir $(NGHTTP2_SRC:.c=.obj)))\r
+NGHTTP2_OBJ_D = $(addprefix $(OBJ_DIR)/d_, $(notdir $(NGHTTP2_SRC:.c=.obj)))\r
+\r
+all: intro $(OBJ_DIR) $(TARGETS)\r
+       @echo 'Welcome to NgHTTP2 (release + debug).'\r
+       @echo 'Do a "make -f Makefile.MSVC install" at own risk!'\r
+\r
+intro:\r
+       @echo 'Building NgHTTP (MSVC) ver. "$(VERSION)".'\r
+\r
+test_ver:\r
+       @echo '$$(VERSION):   "$(VERSION)".'\r
+       @echo '$$(_VERSION):  "$(_VERSION)".'\r
+       @echo '$$(VER_MAJOR): "$(VER_MAJOR)".'\r
+       @echo '$$(VER_MINOR): "$(VER_MINOR)".'\r
+       @echo '$$(VER_MICRO): "$(VER_MICRO)".'\r
+\r
+$(OBJ_DIR):\r
+       - mkdir $(OBJ_DIR)\r
+\r
+install: includes/nghttp2/nghttp2.h includes/nghttp2/nghttp2ver.h \\r
+         nghttp2.dll  nghttp2.lib  nghttp2_imp.lib \\r
+         nghttp2d.dll nghttp2d.lib nghttp2d_imp.lib \\r
+         copy_headers_and_libs install_nghttp2_pyd_$(USE_CYTHON)\r
+\r
+#\r
+# This MUST be done before using the 'install_nghttp2_pyd_1' rule.\r
+#\r
+copy_headers_and_libs:\r
+       - mkdir $(INSTALL_HDR)/nghttp2\r
+       cp --update $(addprefix includes/nghttp2/, nghttp2.h nghttp2ver.h)     $(INSTALL_HDR)/nghttp2\r
+       cp --update nghttp2.dll nghttp2d.dll $(NGHTTP2_PDB_R) $(NGHTTP2_PDB_D) $(INSTALL_BIN)\r
+       cp --update nghttp2.lib nghttp2d.lib nghttp2_imp.lib nghttp2d_imp.lib  $(INSTALL_LIB)\r
+       @echo\r
+\r
+nghttp2.lib: $(NGHTTP2_OBJ_R)\r
+       lib -nologo -out:$@ $^\r
+       @echo\r
+\r
+nghttp2d.lib: $(NGHTTP2_OBJ_D)\r
+       lib -nologo -out:$@ $^\r
+       @echo\r
+\r
+nghttp2.dll nghttp2_imp.lib: $(NGHTTP2_OBJ_R) $(OBJ_DIR)/r_nghttp2.res $(OBJ_DIR)/r_nghttp2.def\r
+       link $(LDFLAGS) -dll -out:nghttp2.dll -implib:nghttp2_imp.lib -def:$(OBJ_DIR)/r_nghttp2.def \\r
+            $(NGHTTP2_OBJ_R) $(OBJ_DIR)/r_nghttp2.res $(EXT_LIBS)\r
+       @echo\r
+\r
+nghttp2d.dll nghttp2d_imp.lib: $(NGHTTP2_OBJ_D) $(OBJ_DIR)/d_nghttp2.res $(OBJ_DIR)/d_nghttp2.def\r
+       link $(LDFLAGS) -dll -out:nghttp2d.dll -implib:nghttp2d_imp.lib -def:$(OBJ_DIR)/d_nghttp2.def \\r
+            $(NGHTTP2_OBJ_D) $(OBJ_DIR)/d_nghttp2.res $(EXT_LIBS)\r
+       @echo\r
+\r
+install_nghttp2_pyd_0: ;\r
+\r
+install_nghttp2_pyd_1: $(addprefix ../python/, setup.py.in nghttp2.pyx)\r
+       cd ../python ; \\r
+       echo '# $(GENERATED). DO NOT EDIT.' > setup.py ; \\r
+       sed -e 's/@top_srcdir@/../'   \\r
+           -e 's/@top_builddir@/../' \\r
+           -e 's/@PACKAGE_VERSION@/$(VERSION)/' setup.py.in >> setup.py ; \\r
+       cython -v nghttp2.pyx ; \\r
+       python setup.py install\r
+\r
+clean_nghttp2_pyd_0: ;\r
+\r
+clean_nghttp2_pyd_1:\r
+       cd ../python ; \\r
+       rm -f setup.py nghttp2.c ; \\r
+       rm -fR build/*\r
+\r
+$(OBJ_DIR)/r_%.obj: %.c\r
+       $(CC) $(CFLAGS_R) $(CFLAGS) -Fo$@ -c $<\r
+       @echo\r
+\r
+$(OBJ_DIR)/d_%.obj: %.c\r
+       $(CC) $(CFLAGS_D) $(CFLAGS) -Fo$@ -c $<\r
+       @echo\r
+\r
+$(OBJ_DIR)/r_nghttp2.res: nghttp2.rc\r
+       rc -nologo -D_RELEASE -Fo $@ $<\r
+       @echo\r
+\r
+$(OBJ_DIR)/d_nghttp2.res: nghttp2.rc\r
+       rc -nologo -D_DEBUG -Fo $@ $<\r
+       @echo\r
+\r
+includes/nghttp2/nghttp2ver.h: includes/nghttp2/nghttp2ver.h.in\r
+       sed < includes/nghttp2/nghttp2ver.h.in     \\r
+            -e 's/@PACKAGE_VERSION@/$(VERSION)/g' \\r
+            -e 's/@PACKAGE_VERSION_NUM@/($(VERSION_NUM))/g' > $@\r
+       touch --reference=includes/nghttp2/nghttp2ver.h.in $@\r
+\r
+\r
+define RES_FILE\r
+  #include <winver.h>\r
+\r
+  VS_VERSION_INFO VERSIONINFO\r
+    FILEVERSION    $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0\r
+    PRODUCTVERSION $(VER_MAJOR), $(VER_MINOR), $(VER_MICRO), 0\r
+    FILEFLAGSMASK  0x3fL\r
+    FILEOS         0x40004L\r
+    FILETYPE       0x2L\r
+    FILESUBTYPE    0x0L\r
+  #ifdef _DEBUG\r
+    #define        VER_STR  "$(VERSION).0 (MSVC debug)"\r
+    #define        DBG      "d"\r
+    FILEFLAGS      0x1L\r
+  #else\r
+    #define        VER_STR  "$(VERSION).0 (MSVC release)"\r
+    #define        DBG      ""\r
+    FILEFLAGS      0x0L\r
+  #endif\r
+  BEGIN\r
+    BLOCK "StringFileInfo"\r
+    BEGIN\r
+      BLOCK "040904b0"\r
+      BEGIN\r
+        VALUE "CompanyName",      "http://tatsuhiro-t.github.io/nghttp2/"\r
+        VALUE "FileDescription",  "nghttp2; HTTP/2 C library"\r
+        VALUE "FileVersion",      VER_STR\r
+        VALUE "InternalName",     "nghttp2" DBG\r
+        VALUE "LegalCopyright",   "The MIT License"\r
+        VALUE "LegalTrademarks",  ""\r
+        VALUE "OriginalFilename", "nghttp2" DBG ".dll"\r
+        VALUE "ProductName",      "NGHTTP2."\r
+        VALUE "ProductVersion",   VER_STR\r
+        VALUE "PrivateBuild",     "The privat build of <gvanem@yahoo.no>."\r
+        VALUE "SpecialBuild",     ""\r
+      END\r
+    END\r
+  BLOCK "VarFileInfo"\r
+  BEGIN\r
+    VALUE "Translation", 0x409, 1200\r
+  END\r
+  END\r
+endef\r
+\r
+export RES_FILE\r
+\r
+nghttp2.rc: Makefile.MSVC\r
+       @echo 'Generating $@...'\r
+       @echo ' /* $(GENERATED). DO NOT EDIT.' > $@\r
+       @echo '  */'                          >> $@\r
+       @echo "$$RES_FILE"                    >> $@\r
+\r
+$(OBJ_DIR)/r_nghttp2.def: Makefile.MSVC\r
+       @echo 'Generating $@...'\r
+       @echo '; $(GENERATED). DO NOT EDIT.' > $@\r
+       @echo ';'                           >> $@\r
+       @echo 'LIBRARY nghttp2.dll'         >> $@\r
+       @echo 'EXPORTS'                     >> $@\r
+       nm $(NGHTTP2_OBJ_R) | grep ' T .*_nghttp2' | sed 's/^.* _/  /' >> $@\r
+       @echo 'NGHTTP2_STATIC_TABLE_LENGTH DATA' >> $@\r
+\r
+$(OBJ_DIR)/d_nghttp2.def: Makefile.MSVC\r
+       @echo 'Generating $@...'\r
+       @echo '; $(GENERATED). DO NOT EDIT.' > $@\r
+       @echo ';'                           >> $@\r
+       @echo 'LIBRARY nghttp2d.dll'        >> $@\r
+       @echo 'EXPORTS'                     >> $@\r
+       nm $(NGHTTP2_OBJ_D) | grep ' T .*_nghttp2' | sed 's/^.* _/  /' >> $@\r
+       @echo 'NGHTTP2_STATIC_TABLE_LENGTH DATA' >> $@\r
+\r
+clean:\r
+       rm -f $(OBJ_DIR)/* nghttp2_imp.exp nghttp2_imp.exp \\r
+             nghttp2.map nghttp2d.map nghttp2.rc includes/nghttp2/nghttp2ver.h\r
+       @echo\r
+\r
+vclean realclean: clean clean_nghttp2_pyd_$(USE_CYTHON)\r
+       rm -f $(TARGETS) nghttp2.pdb nghttp2d.pdb nghttp2_imp.exp nghttp2d_imp.exp .depend.MSVC\r
+       - rmdir $(OBJ_DIR)\r
+\r
+#\r
+# Use gcc to generated the dependencies. No MSVC specific args please!\r
+#\r
+REPLACE_R = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/r_\1.obj: /'\r
+REPLACE_D = 's/\(.*\)\.o: /\n$$(OBJ_DIR)\/d_\1.obj: /'\r
+\r
+depend: includes/nghttp2/nghttp2ver.h\r
+       @echo '# $(GENERATED). DO NOT EDIT.' > .depend.MSVC\r
+       gcc -MM $(CFLAGS) $(NGHTTP2_SRC)    >> .depend.tmp\r
+       @echo '#'                           >> .depend.MSVC\r
+       @echo '# Release lib objects:'      >> .depend.MSVC\r
+       sed -e $(REPLACE_R) .depend.tmp     >> .depend.MSVC\r
+       @echo '#'                           >> .depend.MSVC\r
+       @echo '# Debug lib objects:'        >> .depend.MSVC\r
+       sed -e $(REPLACE_D) .depend.tmp     >> .depend.MSVC\r
+       rm -f .depend.tmp\r
+\r
+-include .depend.MSVC
\ No newline at end of file
diff --git a/lib/includes/Makefile.am b/lib/includes/Makefile.am
new file mode 100644 (file)
index 0000000..80af63c
--- /dev/null
@@ -0,0 +1,23 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+nobase_include_HEADERS = nghttp2/nghttp2.h nghttp2/nghttp2ver.h
diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h
new file mode 100644 (file)
index 0000000..80334d2
--- /dev/null
@@ -0,0 +1,3615 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013, 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_H
+#define NGHTTP2_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <nghttp2/nghttp2ver.h>
+
+/**
+ * @macro
+ *
+ * The protocol version identification string of this library
+ * supports.  This identifier is used if HTTP/2 is used over TLS.
+ */
+#define NGHTTP2_PROTO_VERSION_ID "h2-14"
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_PROTO_VERSION_ID`.
+ */
+#define NGHTTP2_PROTO_VERSION_ID_LEN 5
+
+/**
+ * @macro
+ *
+ * The seriazlied form of ALPN protocol identifier this library
+ * supports.  Notice that first byte is the length of following
+ * protocol identifier.  This is the same wire format of `TLS ALPN
+ * extension <https://tools.ietf.org/html/rfc7301>`_.  This is useful
+ * to process incoming ALPN tokens in wire format.
+ */
+#define NGHTTP2_PROTO_ALPN "\x5h2-14"
+
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_PROTO_ALPN`.
+ */
+#define NGHTTP2_PROTO_ALPN_LEN (sizeof(NGHTTP2_PROTO_ALPN) - 1)
+
+/**
+ * @macro
+ *
+ * The protocol version identification string of this library
+ * supports.  This identifier is used if HTTP/2 is used over cleartext
+ * TCP.
+ */
+#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "h2c-14"
+
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_CLEARTEXT_PROTO_VERSION_ID`.
+ */
+#define NGHTTP2_CLEARTEXT_PROTO_VERSION_ID_LEN 6
+
+struct nghttp2_session;
+/**
+ * @struct
+ *
+ * The primary structure to hold the resources needed for a HTTP/2
+ * session.  The details of this structure are intentionally hidden
+ * from the public API.
+ */
+typedef struct nghttp2_session nghttp2_session;
+
+/**
+ * @macro
+ *
+ * The age of :type:`nghttp2_info`
+ */
+#define NGHTTP2_VERSION_AGE 1
+
+/**
+ * @struct
+ *
+ * This struct is what `nghttp2_version()` returns.  It holds
+ * information about the particular nghttp2 version.
+ */
+typedef struct {
+  /**
+   * Age of this struct.  This instance of nghttp2 sets it to
+   * :macro:`NGHTTP2_VERSION_AGE` but a future version may bump it and
+   * add more struct fields at the bottom
+   */
+  int age;
+  /**
+   * the :macro:`NGHTTP2_VERSION_NUM` number (since age ==1)
+   */
+  int version_num;
+  /**
+   * points to the :macro:`NGHTTP2_VERSION` string (since age ==1)
+   */
+  const char *version_str;
+  /**
+   * points to the :macro:`NGHTTP2_PROTO_VERSION_ID` string this
+   * instance implements (since age ==1)
+   */
+  const char *proto_str;
+  /* -------- the above fields all exist when age == 1 */
+} nghttp2_info;
+
+/**
+ * @macro
+ *
+ * The default weight of stream dependency.
+ */
+#define NGHTTP2_DEFAULT_WEIGHT 16
+
+/**
+ * @macro
+ *
+ * The maximum weight of stream dependency.
+ */
+#define NGHTTP2_MAX_WEIGHT 256
+
+/**
+ * @macro
+ *
+ * The minimum weight of stream dependency.
+ */
+#define NGHTTP2_MIN_WEIGHT 1
+
+/**
+ * @macro
+ *
+ * The maximum window size
+ */
+#define NGHTTP2_MAX_WINDOW_SIZE ((int32_t)((1U << 31) - 1))
+
+/**
+ * @macro
+ *
+ * The initial window size for stream level flow control.
+ */
+#define NGHTTP2_INITIAL_WINDOW_SIZE ((1 << 16) - 1)
+/**
+ * @macro
+ *
+ * The initial window size for connection level flow control.
+ */
+#define NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ((1 << 16) - 1)
+
+/**
+ * @macro
+ *
+ * The default header table size.
+ */
+#define NGHTTP2_DEFAULT_HEADER_TABLE_SIZE (1 << 12)
+
+/**
+ * @macro
+ *
+ * The client connection preface.
+ */
+#define NGHTTP2_CLIENT_CONNECTION_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_CLIENT_CONNECTION_PREFACE`.
+ */
+#define NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN 24
+
+/**
+ * @macro
+ *
+ * The client connection header.  This macro is obsoleted by
+ * NGHTTP2_CLIENT_CONNECTION_PREFACE.
+ */
+#define NGHTTP2_CLIENT_CONNECTION_HEADER NGHTTP2_CLIENT_CONNECTION_PREFACE
+
+/**
+ * @macro
+ *
+ * The length of :macro:`NGHTTP2_CLIENT_CONNECTION_HEADER`.
+ */
+#define NGHTTP2_CLIENT_CONNECTION_HEADER_LEN                                   \
+  NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN
+
+/**
+ * @enum
+ *
+ * Error codes used in this library.  The code range is [-999, -500],
+ * inclusive. The following values are defined:
+ */
+typedef enum {
+  /**
+   * Invalid argument passed.
+   */
+  NGHTTP2_ERR_INVALID_ARGUMENT = -501,
+  /**
+   * Out of buffer space.
+   */
+  NGHTTP2_ERR_BUFFER_ERROR = -502,
+  /**
+   * The specified protocol version is not supported.
+   */
+  NGHTTP2_ERR_UNSUPPORTED_VERSION = -503,
+  /**
+   * Used as a return value from :type:`nghttp2_send_callback` and
+   * :type:`nghttp2_recv_callback` to indicate that the operation
+   * would block.
+   */
+  NGHTTP2_ERR_WOULDBLOCK = -504,
+  /**
+   * General protocol error
+   */
+  NGHTTP2_ERR_PROTO = -505,
+  /**
+   * The frame is invalid.
+   */
+  NGHTTP2_ERR_INVALID_FRAME = -506,
+  /**
+   * The peer performed a shutdown on the connection.
+   */
+  NGHTTP2_ERR_EOF = -507,
+  /**
+   * Used as a return value from
+   * :func:`nghttp2_data_source_read_callback` to indicate that data
+   * transfer is postponed.  See
+   * :func:`nghttp2_data_source_read_callback` for details.
+   */
+  NGHTTP2_ERR_DEFERRED = -508,
+  /**
+   * Stream ID has reached the maximum value.  Therefore no stream ID
+   * is available.
+   */
+  NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE = -509,
+  /**
+   * The stream is already closed; or the stream ID is invalid.
+   */
+  NGHTTP2_ERR_STREAM_CLOSED = -510,
+  /**
+   * RST_STREAM has been added to the outbound queue.  The stream is
+   * in closing state.
+   */
+  NGHTTP2_ERR_STREAM_CLOSING = -511,
+  /**
+   * The transmission is not allowed for this stream (e.g., a frame
+   * with END_STREAM flag set has already sent).
+   */
+  NGHTTP2_ERR_STREAM_SHUT_WR = -512,
+  /**
+   * The stream ID is invalid.
+   */
+  NGHTTP2_ERR_INVALID_STREAM_ID = -513,
+  /**
+   * The state of the stream is not valid (e.g., DATA cannot be sent
+   * to the stream if response HEADERS has not been sent).
+   */
+  NGHTTP2_ERR_INVALID_STREAM_STATE = -514,
+  /**
+   * Another DATA frame has already been deferred.
+   */
+  NGHTTP2_ERR_DEFERRED_DATA_EXIST = -515,
+  /**
+   * Starting new stream is not allowed (e.g., GOAWAY has been sent
+   * and/or received).
+   */
+  NGHTTP2_ERR_START_STREAM_NOT_ALLOWED = -516,
+  /**
+   * GOAWAY has already been sent.
+   */
+  NGHTTP2_ERR_GOAWAY_ALREADY_SENT = -517,
+  /**
+   * The received frame contains the invalid header block (e.g., There
+   * are duplicate header names; or the header names are not encoded
+   * in US-ASCII character set and not lower cased; or the header name
+   * is zero-length string; or the header value contains multiple
+   * in-sequence NUL bytes).
+   */
+  NGHTTP2_ERR_INVALID_HEADER_BLOCK = -518,
+  /**
+   * Indicates that the context is not suitable to perform the
+   * requested operation.
+   */
+  NGHTTP2_ERR_INVALID_STATE = -519,
+  /**
+   * The user callback function failed due to the temporal error.
+   */
+  NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE = -521,
+  /**
+   * The length of the frame is invalid, either too large or too small.
+   */
+  NGHTTP2_ERR_FRAME_SIZE_ERROR = -522,
+  /**
+   * Header block inflate/deflate error.
+   */
+  NGHTTP2_ERR_HEADER_COMP = -523,
+  /**
+   * Flow control error
+   */
+  NGHTTP2_ERR_FLOW_CONTROL = -524,
+  /**
+   * Insufficient buffer size given to function.
+   */
+  NGHTTP2_ERR_INSUFF_BUFSIZE = -525,
+  /**
+   * Callback was paused by the application
+   */
+  NGHTTP2_ERR_PAUSE = -526,
+  /**
+   * There are too many in-flight SETTING frame and no more
+   * transmission of SETTINGS is allowed.
+   */
+  NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS = -527,
+  /**
+   * The server push is disabled.
+   */
+  NGHTTP2_ERR_PUSH_DISABLED = -528,
+  /**
+   * DATA frame for a given stream has been already submitted and has
+   * not been fully processed yet.
+   */
+  NGHTTP2_ERR_DATA_EXIST = -529,
+  /**
+   * The current session is closing due to a connection error or
+   * `nghttp2_session_terminate_session()` is called.
+   */
+  NGHTTP2_ERR_SESSION_CLOSING = -530,
+  /**
+   * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
+   * under unexpected condition and processing was terminated (e.g.,
+   * out of memory).  If application receives this error code, it must
+   * stop using that :type:`nghttp2_session` object and only allowed
+   * operation for that object is deallocate it using
+   * `nghttp2_session_del()`.
+   */
+  NGHTTP2_ERR_FATAL = -900,
+  /**
+   * Out of memory.  This is a fatal error.
+   */
+  NGHTTP2_ERR_NOMEM = -901,
+  /**
+   * The user callback function failed.  This is a fatal error.
+   */
+  NGHTTP2_ERR_CALLBACK_FAILURE = -902,
+  /**
+   * Invalid connection preface was received and further processing is
+   * not possible.
+   */
+  NGHTTP2_ERR_BAD_PREFACE = -903
+} nghttp2_error;
+
+/**
+ * @enum
+ *
+ * The flags for header field name/value pair.
+ */
+typedef enum {
+  /**
+   * No flag set.
+   */
+  NGHTTP2_NV_FLAG_NONE = 0,
+  /**
+   * Indicates that this name/value pair must not be indexed.
+   */
+  NGHTTP2_NV_FLAG_NO_INDEX = 0x01
+} nghttp2_nv_flag;
+
+/**
+ * @struct
+ *
+ * The name/value pair, which mainly used to represent header fields.
+ */
+typedef struct {
+  /**
+   * The |name| byte string, which is not necessarily ``NULL``
+   * terminated.
+   */
+  uint8_t *name;
+  /**
+   * The |value| byte string, which is not necessarily ``NULL``
+   * terminated.
+   */
+  uint8_t *value;
+  /**
+   * The length of the |name|.
+   */
+  size_t namelen;
+  /**
+   * The length of the |value|.
+   */
+  size_t valuelen;
+  /**
+   * Bitwise OR of one or more of :type:`nghttp2_nv_flag`.
+   */
+  uint8_t flags;
+} nghttp2_nv;
+
+/**
+ * @enum
+ *
+ * The frame types in HTTP/2 specification.
+ */
+typedef enum {
+  /**
+   * The DATA frame.
+   */
+  NGHTTP2_DATA = 0,
+  /**
+   * The HEADERS frame.
+   */
+  NGHTTP2_HEADERS = 0x01,
+  /**
+   * The PRIORITY frame.
+   */
+  NGHTTP2_PRIORITY = 0x02,
+  /**
+   * The RST_STREAM frame.
+   */
+  NGHTTP2_RST_STREAM = 0x03,
+  /**
+   * The SETTINGS frame.
+   */
+  NGHTTP2_SETTINGS = 0x04,
+  /**
+   * The PUSH_PROMISE frame.
+   */
+  NGHTTP2_PUSH_PROMISE = 0x05,
+  /**
+   * The PING frame.
+   */
+  NGHTTP2_PING = 0x06,
+  /**
+   * The GOAWAY frame.
+   */
+  NGHTTP2_GOAWAY = 0x07,
+  /**
+   * The WINDOW_UPDATE frame.
+   */
+  NGHTTP2_WINDOW_UPDATE = 0x08,
+  /**
+   * The CONTINUATION frame.
+   */
+  NGHTTP2_CONTINUATION = 0x09
+} nghttp2_frame_type;
+
+/**
+ * @enum
+ *
+ * The extension frame types.
+ *
+ * TODO: The assigned frame types were carried from draft-12, and now
+ * actually TBD.
+ */
+typedef enum {
+  /**
+   * The ALTSVC extension frame.
+   */
+  NGHTTP2_EXT_ALTSVC = 0x0a
+} nghttp2_ext_frame_type;
+
+/**
+ * @enum
+ *
+ * The flags for HTTP/2 frames.  This enum defines all flags for all
+ * frames.
+ */
+typedef enum {
+  /**
+   * No flag set.
+   */
+  NGHTTP2_FLAG_NONE = 0,
+  /**
+   * The END_STREAM flag.
+   */
+  NGHTTP2_FLAG_END_STREAM = 0x01,
+  /**
+   * The END_HEADERS flag.
+   */
+  NGHTTP2_FLAG_END_HEADERS = 0x04,
+  /**
+   * The ACK flag.
+   */
+  NGHTTP2_FLAG_ACK = 0x01,
+  /**
+   * The PADDED flag.
+   */
+  NGHTTP2_FLAG_PADDED = 0x08,
+  /**
+   * The PRIORITY flag.
+   */
+  NGHTTP2_FLAG_PRIORITY = 0x20
+} nghttp2_flag;
+
+/**
+ * @enum
+ * The SETTINGS ID.
+ */
+typedef enum {
+  /**
+   * SETTINGS_HEADER_TABLE_SIZE
+   */
+  NGHTTP2_SETTINGS_HEADER_TABLE_SIZE = 0x01,
+  /**
+   * SETTINGS_ENABLE_PUSH
+   */
+  NGHTTP2_SETTINGS_ENABLE_PUSH = 0x02,
+  /**
+   * SETTINGS_MAX_CONCURRENT_STREAMS
+   */
+  NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS = 0x03,
+  /**
+   * SETTINGS_INITIAL_WINDOW_SIZE
+   */
+  NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04,
+  /**
+   * SETTINGS_MAX_FRAME_SIZE
+   */
+  NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 0x05,
+  /**
+   * SETTINGS_MAX_HEADER_LIST_SIZE
+   */
+  NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06
+} nghttp2_settings_id;
+/* Note: If we add SETTINGS, update the capacity of
+   NGHTTP2_INBOUND_NUM_IV as well */
+
+/**
+ * @macro
+ * Default maximum concurrent streams.
+ */
+#define NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ((1U << 31) - 1)
+
+/**
+ * @enum
+ * The status codes for the RST_STREAM and GOAWAY frames.
+ */
+typedef enum {
+  /**
+   * No errors.
+   */
+  NGHTTP2_NO_ERROR = 0x00,
+  /**
+   * PROTOCOL_ERROR
+   */
+  NGHTTP2_PROTOCOL_ERROR = 0x01,
+  /**
+   * INTERNAL_ERROR
+   */
+  NGHTTP2_INTERNAL_ERROR = 0x02,
+  /**
+   * FLOW_CONTROL_ERROR
+   */
+  NGHTTP2_FLOW_CONTROL_ERROR = 0x03,
+  /**
+   * SETTINGS_TIMEOUT
+   */
+  NGHTTP2_SETTINGS_TIMEOUT = 0x04,
+  /**
+   * STREAM_CLOSED
+   */
+  NGHTTP2_STREAM_CLOSED = 0x05,
+  /**
+   * FRAME_SIZE_ERROR
+   */
+  NGHTTP2_FRAME_SIZE_ERROR = 0x06,
+  /**
+   * REFUSED_STREAM
+   */
+  NGHTTP2_REFUSED_STREAM = 0x07,
+  /**
+   * CANCEL
+   */
+  NGHTTP2_CANCEL = 0x08,
+  /**
+   * COMPRESSION_ERROR
+   */
+  NGHTTP2_COMPRESSION_ERROR = 0x09,
+  /**
+   * CONNECT_ERROR
+   */
+  NGHTTP2_CONNECT_ERROR = 0x0a,
+  /**
+   * ENHANCE_YOUR_CALM
+   */
+  NGHTTP2_ENHANCE_YOUR_CALM = 0x0b,
+  /**
+   * INADEQUATE_SECURITY
+   */
+  NGHTTP2_INADEQUATE_SECURITY = 0x0c,
+  /**
+   * HTTP_1_1_REQUIRED
+   */
+  NGHTTP2_HTTP_1_1_REQUIRED = 0x0d
+} nghttp2_error_code;
+
+/**
+ * @struct
+ * The frame header.
+ */
+typedef struct {
+  /**
+   * The length field of this frame, excluding frame header.
+   */
+  size_t length;
+  /**
+   * The stream identifier (aka, stream ID)
+   */
+  int32_t stream_id;
+  /**
+   * The type of this frame.  See `nghttp2_frame_type`.
+   */
+  uint8_t type;
+  /**
+   * The flags.
+   */
+  uint8_t flags;
+  /**
+   * Reserved bit in frame header.  Currently, this is always set to 0
+   * and application should not expect something useful in here.
+   */
+  uint8_t reserved;
+} nghttp2_frame_hd;
+
+/**
+ * @union
+ *
+ * This union represents the some kind of data source passed to
+ * :type:`nghttp2_data_source_read_callback`.
+ */
+typedef union {
+  /**
+   * The integer field, suitable for a file descriptor.
+   */
+  int fd;
+  /**
+   * The pointer to an arbitrary object.
+   */
+  void *ptr;
+} nghttp2_data_source;
+
+/**
+ * @enum
+ *
+ * The flags used to set in |data_flags| output parameter in
+ * :type:`nghttp2_data_source_read_callback`.
+ */
+typedef enum {
+  /**
+   * No flag set.
+   */
+  NGHTTP2_DATA_FLAG_NONE = 0,
+  /**
+   * Indicates EOF was sensed.
+   */
+  NGHTTP2_DATA_FLAG_EOF = 0x01
+} nghttp2_data_flag;
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when the library wants to read data from
+ * the |source|.  The read data is sent in the stream |stream_id|.
+ * The implementation of this function must read at most |length|
+ * bytes of data from |source| (or possibly other places) and store
+ * them in |buf| and return number of data stored in |buf|.  If EOF is
+ * reached, set :enum:`NGHTTP2_DATA_FLAG_EOF` flag in |*data_flags|.
+ *
+ * If the application wants to postpone DATA frames (e.g.,
+ * asynchronous I/O, or reading data blocks for long time), it is
+ * achieved by returning :enum:`NGHTTP2_ERR_DEFERRED` without reading
+ * any data in this invocation.  The library removes DATA frame from
+ * the outgoing queue temporarily.  To move back deferred DATA frame
+ * to outgoing queue, call `nghttp2_session_resume_data()`.  In case
+ * of error, there are 2 choices. Returning
+ * :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will close the stream
+ * by issuing RST_STREAM with :enum:`NGHTTP2_INTERNAL_ERROR`.  If a
+ * different error code is desirable, use
+ * `nghttp2_submit_rst_stream()` with a desired error code and then
+ * return :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.  Returning
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` will signal the entire session
+ * failure.
+ */
+typedef ssize_t (*nghttp2_data_source_read_callback)(
+    nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length,
+    uint32_t *data_flags, nghttp2_data_source *source, void *user_data);
+
+/**
+ * @struct
+ *
+ * This struct represents the data source and the way to read a chunk
+ * of data from it.
+ */
+typedef struct {
+  /**
+   * The data source.
+   */
+  nghttp2_data_source source;
+  /**
+   * The callback function to read a chunk of data from the |source|.
+   */
+  nghttp2_data_source_read_callback read_callback;
+} nghttp2_data_provider;
+
+/**
+ * @struct
+ *
+ * The DATA frame.  The received data is delivered via
+ * :type:`nghttp2_on_data_chunk_recv_callback`.
+ */
+typedef struct {
+  nghttp2_frame_hd hd;
+  /**
+   * The length of the padding in this frame.  This includes PAD_HIGH
+   * and PAD_LOW.
+   */
+  size_t padlen;
+} nghttp2_data;
+
+/**
+ * @enum
+ *
+ * The category of HEADERS, which indicates the role of the frame.  In
+ * HTTP/2 spec, request, response, push response and other arbitrary
+ * headers (e.g., trailers) are all called just HEADERS.  To give the
+ * application the role of incoming HEADERS frame, we define several
+ * categories.
+ */
+typedef enum {
+  /**
+   * The HEADERS frame is opening new stream, which is analogous to
+   * SYN_STREAM in SPDY.
+   */
+  NGHTTP2_HCAT_REQUEST = 0,
+  /**
+   * The HEADERS frame is the first response headers, which is
+   * analogous to SYN_REPLY in SPDY.
+   */
+  NGHTTP2_HCAT_RESPONSE = 1,
+  /**
+   * The HEADERS frame is the first headers sent against reserved
+   * stream.
+   */
+  NGHTTP2_HCAT_PUSH_RESPONSE = 2,
+  /**
+   * The HEADERS frame which does not apply for the above categories,
+   * which is analogous to HEADERS in SPDY.  If non-final response
+   * (e.g., status 1xx) is used, final response HEADERS frame will be
+   * categorized here.
+   */
+  NGHTTP2_HCAT_HEADERS = 3
+} nghttp2_headers_category;
+
+/**
+ * @struct
+ *
+ * The structure to specify stream dependency.
+ */
+typedef struct {
+  /**
+   * The stream ID of the stream to depend on.  Specifying 0 makes
+   * stream not depend any other stream.
+   */
+  int32_t stream_id;
+  /**
+   * The weight of this dependency.
+   */
+  int32_t weight;
+  /**
+   * nonzero means exclusive dependency
+   */
+  uint8_t exclusive;
+} nghttp2_priority_spec;
+
+/**
+ * @struct
+ *
+ * The HEADERS frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The length of the padding in this frame.  This includes PAD_HIGH
+   * and PAD_LOW.
+   */
+  size_t padlen;
+  /**
+   * The priority specification
+   */
+  nghttp2_priority_spec pri_spec;
+  /**
+   * The name/value pairs.
+   */
+  nghttp2_nv *nva;
+  /**
+   * The number of name/value pairs in |nva|.
+   */
+  size_t nvlen;
+  /**
+   * The category of this HEADERS frame.
+   */
+  nghttp2_headers_category cat;
+} nghttp2_headers;
+
+/**
+ * @struct
+ *
+ * The PRIORITY frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The priority specification.
+   */
+  nghttp2_priority_spec pri_spec;
+} nghttp2_priority;
+
+/**
+ * @struct
+ *
+ * The RST_STREAM frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The error code.  See :type:`nghttp2_error_code`.
+   */
+  uint32_t error_code;
+} nghttp2_rst_stream;
+
+/**
+ * @struct
+ *
+ * The SETTINGS ID/Value pair.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The SETTINGS ID.  See :type:`nghttp2_settings_id`.
+   */
+  int32_t settings_id;
+  /**
+   * The value of this entry.
+   */
+  uint32_t value;
+} nghttp2_settings_entry;
+
+/**
+ * @struct
+ *
+ * The SETTINGS frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The number of SETTINGS ID/Value pairs in |iv|.
+   */
+  size_t niv;
+  /**
+   * The pointer to the array of SETTINGS ID/Value pair.
+   */
+  nghttp2_settings_entry *iv;
+} nghttp2_settings;
+
+/**
+ * @struct
+ *
+ * The PUSH_PROMISE frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The length of the padding in this frame.  This includes PAD_HIGH
+   * and PAD_LOW.
+   */
+  size_t padlen;
+  /**
+   * The name/value pairs.
+   */
+  nghttp2_nv *nva;
+  /**
+   * The number of name/value pairs in |nva|.
+   */
+  size_t nvlen;
+  /**
+   * The promised stream ID
+   */
+  int32_t promised_stream_id;
+  /**
+   * Reserved bit.  Currently this is always set to 0 and application
+   * should not expect something useful in here.
+   */
+  uint8_t reserved;
+} nghttp2_push_promise;
+
+/**
+ * @struct
+ *
+ * The PING frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The opaque data
+   */
+  uint8_t opaque_data[8];
+} nghttp2_ping;
+
+/**
+ * @struct
+ *
+ * The GOAWAY frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The last stream stream ID.
+   */
+  int32_t last_stream_id;
+  /**
+   * The error code.  See :type:`nghttp2_error_code`.
+   */
+  uint32_t error_code;
+  /**
+   * The additional debug data
+   */
+  uint8_t *opaque_data;
+  /**
+   * The length of |opaque_data| member.
+   */
+  size_t opaque_data_len;
+  /**
+   * Reserved bit.  Currently this is always set to 0 and application
+   * should not expect something useful in here.
+   */
+  uint8_t reserved;
+} nghttp2_goaway;
+
+/**
+ * @struct
+ *
+ * The WINDOW_UPDATE frame.  It has the following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The window size increment.
+   */
+  int32_t window_size_increment;
+  /**
+   * Reserved bit.  Currently this is always set to 0 and application
+   * should not expect something useful in here.
+   */
+  uint8_t reserved;
+} nghttp2_window_update;
+
+/**
+ * @struct
+ *
+ * The extension frame.  It has following members:
+ */
+typedef struct {
+  /**
+   * The frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The pointer to extension payload.  The exact pointer type is
+   * determined by hd.type.
+   *
+   * If hd.type == :enum:`NGHTTP2_EXT_ALTSVC`, it is a pointer to
+   * :type:`nghttp2_ext_altsvc`.
+   */
+  void *payload;
+} nghttp2_extension;
+
+/**
+ * @struct
+ *
+ * The ALTSVC extension frame payload.  It has following members:
+ */
+typedef struct {
+  /**
+   * Protocol ID
+   */
+  uint8_t *protocol_id;
+  /**
+   * Host
+   */
+  uint8_t *host;
+  /**
+   * Origin
+   */
+  uint8_t *origin;
+  /**
+   * The length of |protocol_id|
+   */
+  size_t protocol_id_len;
+  /**
+   * The length of |host|
+   */
+  size_t host_len;
+  /**
+   * The length of |origin|
+   */
+  size_t origin_len;
+  /**
+   * Max-Age
+   */
+  uint32_t max_age;
+  /**
+   * Port
+   */
+  uint16_t port;
+} nghttp2_ext_altsvc;
+
+/**
+ * @union
+ *
+ * This union includes all frames to pass them to various function
+ * calls as nghttp2_frame type.  The CONTINUATION frame is omitted
+ * from here because the library deals with it internally.
+ */
+typedef union {
+  /**
+   * The frame header, which is convenient to inspect frame header.
+   */
+  nghttp2_frame_hd hd;
+  /**
+   * The DATA frame.
+   */
+  nghttp2_data data;
+  /**
+   * The HEADERS frame.
+   */
+  nghttp2_headers headers;
+  /**
+   * The PRIORITY frame.
+   */
+  nghttp2_priority priority;
+  /**
+   * The RST_STREAM frame.
+   */
+  nghttp2_rst_stream rst_stream;
+  /**
+   * The SETTINGS frame.
+   */
+  nghttp2_settings settings;
+  /**
+   * The PUSH_PROMISE frame.
+   */
+  nghttp2_push_promise push_promise;
+  /**
+   * The PING frame.
+   */
+  nghttp2_ping ping;
+  /**
+   * The GOAWAY frame.
+   */
+  nghttp2_goaway goaway;
+  /**
+   * The WINDOW_UPDATE frame.
+   */
+  nghttp2_window_update window_update;
+  /**
+   * The extension frame.
+   */
+  nghttp2_extension ext;
+} nghttp2_frame;
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when |session| wants to send data to the
+ * remote peer.  The implementation of this function must send at most
+ * |length| bytes of data stored in |data|.  The |flags| is currently
+ * not used and always 0. It must return the number of bytes sent if
+ * it succeeds.  If it cannot send any single byte without blocking,
+ * it must return :enum:`NGHTTP2_ERR_WOULDBLOCK`.  For other errors,
+ * it must return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.  The
+ * |user_data| pointer is the third argument passed in to the call to
+ * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`.
+ *
+ * This callback is required if the application uses
+ * `nghttp2_session_send()` to send data to the remote endpoint.  If
+ * the application uses solely `nghttp2_session_mem_send()` instead,
+ * this callback function is unnecessary.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_send_callback()`.
+ */
+typedef ssize_t (*nghttp2_send_callback)(nghttp2_session *session,
+                                         const uint8_t *data, size_t length,
+                                         int flags, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when |session| wants to receive data from
+ * the remote peer.  The implementation of this function must read at
+ * most |length| bytes of data and store it in |buf|.  The |flags| is
+ * currently not used and always 0.  It must return the number of
+ * bytes written in |buf| if it succeeds.  If it cannot read any
+ * single byte without blocking, it must return
+ * :enum:`NGHTTP2_ERR_WOULDBLOCK`.  If it gets EOF before it reads any
+ * single byte, it must return :enum:`NGHTTP2_ERR_EOF`.  For other
+ * errors, it must return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ * Returning 0 is treated as :enum:`NGHTTP2_ERR_WOULDBLOCK`.  The
+ * |user_data| pointer is the third argument passed in to the call to
+ * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`.
+ *
+ * This callback is required if the application uses
+ * `nghttp2_session_recv()` to receive data from the remote endpoint.
+ * If the application uses solely `nghttp2_session_mem_recv()`
+ * instead, this callback function is unnecessary.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_recv_callback()`.
+ */
+typedef ssize_t (*nghttp2_recv_callback)(nghttp2_session *session, uint8_t *buf,
+                                         size_t length, int flags,
+                                         void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked by `nghttp2_session_recv()` when a frame
+ * is received.  The |user_data| pointer is the third argument passed
+ * in to the call to `nghttp2_session_client_new()` or
+ * `nghttp2_session_server_new()`.
+ *
+ * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen``
+ * member of their data structure are always ``NULL`` and 0
+ * respectively.  The header name/value pairs are emitted via
+ * :type:`nghttp2_on_header_callback`.
+ *
+ * For HEADERS, PUSH_PROMISE and DATA frames, this callback may be
+ * called after stream is closed (see
+ * :type:`nghttp2_on_stream_close_callback`).  The application should
+ * check that stream is still alive using its own stream management or
+ * :func:`nghttp2_session_get_stream_user_data()`.
+ *
+ * Only HEADERS and DATA frame can signal the end of incoming data.
+ * If ``frame->hd.flags & NGHTTP2_FLAG_END_STREAM`` is nonzero, the
+ * |frame| is the last frame from the remote peer in this stream.
+ *
+ * This callback won't be called for CONTINUATION frames.
+ * HEADERS/PUSH_PROMISE + CONTINUATIONs are treated as single frame.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero value is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_frame_recv_callback()`.
+ */
+typedef int (*nghttp2_on_frame_recv_callback)(nghttp2_session *session,
+                                              const nghttp2_frame *frame,
+                                              void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked by `nghttp2_session_recv()` when an
+ * invalid non-DATA frame is received.  The |error_code| indicates the
+ * error.  It is usually one of the :enum:`nghttp2_error_code` but
+ * that is not guaranteed.  When this callback function is invoked,
+ * the library automatically submits either RST_STREAM or GOAWAY
+ * frame.  The |user_data| pointer is the third argument passed in to
+ * the call to `nghttp2_session_client_new()` or
+ * `nghttp2_session_server_new()`.
+ *
+ * If frame is HEADERS or PUSH_PROMISE, the ``nva`` and ``nvlen``
+ * member of their data structure are always ``NULL`` and 0
+ * respectively.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_invalid_frame_recv_callback()`.
+ */
+typedef int (*nghttp2_on_invalid_frame_recv_callback)(
+    nghttp2_session *session, const nghttp2_frame *frame, uint32_t error_code,
+    void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when a chunk of data in DATA frame is
+ * received.  The |stream_id| is the stream ID this DATA frame belongs
+ * to.  The |flags| is the flags of DATA frame which this data chunk
+ * is contained.  ``(flags & NGHTTP2_FLAG_END_STREAM) != 0`` does not
+ * necessarily mean this chunk of data is the last one in the stream.
+ * You should use :type:`nghttp2_on_frame_recv_callback` to know all
+ * data frames are received.  The |user_data| pointer is the third
+ * argument passed in to the call to `nghttp2_session_client_new()` or
+ * `nghttp2_session_server_new()`.
+ *
+ * If the application uses `nghttp2_session_mem_recv()`, it can return
+ * :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()`
+ * return without processing further input bytes.  The memory by
+ * pointed by the |data| is retained until
+ * `nghttp2_session_mem_recv()` or `nghttp2_session_recv()` is called.
+ * The application must retain the input bytes which was used to
+ * produce the |data| parameter, because it may refer to the memory
+ * region included in the input bytes.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_data_chunk_recv_callback()`.
+ */
+typedef int (*nghttp2_on_data_chunk_recv_callback)(nghttp2_session *session,
+                                                   uint8_t flags,
+                                                   int32_t stream_id,
+                                                   const uint8_t *data,
+                                                   size_t len, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked just before the non-DATA frame |frame| is
+ * sent.  The |user_data| pointer is the third argument passed in to
+ * the call to `nghttp2_session_client_new()` or
+ * `nghttp2_session_server_new()`.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_before_frame_send_callback()`.
+ */
+typedef int (*nghttp2_before_frame_send_callback)(nghttp2_session *session,
+                                                  const nghttp2_frame *frame,
+                                                  void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked after the frame |frame| is sent.  The
+ * |user_data| pointer is the third argument passed in to the call to
+ * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_frame_send_callback()`.
+ */
+typedef int (*nghttp2_on_frame_send_callback)(nghttp2_session *session,
+                                              const nghttp2_frame *frame,
+                                              void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked after the non-DATA frame |frame| is not
+ * sent because of the error.  The error is indicated by the
+ * |lib_error_code|, which is one of the values defined in
+ * :type:`nghttp2_error`.  The |user_data| pointer is the third
+ * argument passed in to the call to `nghttp2_session_client_new()` or
+ * `nghttp2_session_server_new()`.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_frame_not_send_callback()`.
+ */
+typedef int (*nghttp2_on_frame_not_send_callback)(nghttp2_session *session,
+                                                  const nghttp2_frame *frame,
+                                                  int lib_error_code,
+                                                  void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when the stream |stream_id| is closed.
+ * The reason of closure is indicated by the |error_code|.  The
+ * |error_code| is usually one of :enum:`nghttp2_error_code`, but that
+ * is not guaranteed.  The stream_user_data, which was specified in
+ * `nghttp2_submit_request()` or `nghttp2_submit_headers()`, is still
+ * available in this function.  The |user_data| pointer is the third
+ * argument passed in to the call to `nghttp2_session_client_new()` or
+ * `nghttp2_session_server_new()`.
+ *
+ * This function is also called for a stream in reserved state.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_stream_close_callback()`.
+ */
+typedef int (*nghttp2_on_stream_close_callback)(nghttp2_session *session,
+                                                int32_t stream_id,
+                                                uint32_t error_code,
+                                                void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when the reception of header block in
+ * HEADERS or PUSH_PROMISE is started.  Each header name/value pair
+ * will be emitted by :type:`nghttp2_on_header_callback`.
+ *
+ * The ``frame->hd.flags`` may not have
+ * :enum:`NGHTTP2_FLAG_END_HEADERS` flag set, which indicates that one
+ * or more CONTINUATION frames are involved.  But the application does
+ * not need to care about that because the header name/value pairs are
+ * emitted transparently regardless of CONTINUATION frames.
+ *
+ * The implementation of this function must return 0 if it succeeds or
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.  If nonzero value other than
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` is returned, it is treated as
+ * if :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` is returned.  If
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` is returned,
+ * `nghttp2_session_mem_recv()` function will immediately return
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_begin_headers_callback()`.
+ */
+typedef int (*nghttp2_on_begin_headers_callback)(nghttp2_session *session,
+                                                 const nghttp2_frame *frame,
+                                                 void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when a header name/value pair is received
+ * for the |frame|.  The |name| of length |namelen| is header name.
+ * The |value| of length |valuelen| is header value.  The |flags| is
+ * bitwise OR of one or more of :type:`nghttp2_nv_flag`.
+ *
+ * If :enum:`NGHTTP2_NV_FLAG_NO_INDEX` is set in |flags|, the receiver
+ * must not index this name/value pair when forwarding it to the next
+ * hop.
+ *
+ * When this callback is invoked, ``frame->hd.type`` is either
+ * :enum:`NGHTTP2_HEADERS` or :enum:`NGHTTP2_PUSH_PROMISE`.  After all
+ * header name/value pairs are processed with this callback, and no
+ * error has been detected, :type:`nghttp2_on_frame_recv_callback`
+ * will be invoked.  If there is an error in decompression,
+ * :type:`nghttp2_on_frame_recv_callback` for the |frame| will not be
+ * invoked.
+ *
+ * The |name| may be ``NULL`` if the |namelen| is 0.  The same thing
+ * can be said about the |value|.
+ *
+ * Please note that nghttp2 library does not perform any validity
+ * check against the |name| and the |value|.  For example, the
+ * |namelen| could be 0, and/or the |value| contains ``0x0a`` or
+ * ``0x0d``.  The application must check them if it matters.  The
+ * helper function `nghttp2_check_header_name()` and
+ * `nghttp2_check_header_value()` provide simple validation against
+ * HTTP2 header field construction rule.
+ *
+ * HTTP/2 specification requires that pseudo header fields (header
+ * field starting with ':') must appear in front of regular header
+ * fields.  The library does not validate this requirement.  The
+ * application must check them if it matters.
+ *
+ * If the application uses `nghttp2_session_mem_recv()`, it can return
+ * :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()`
+ * return without processing further input bytes.  The memory pointed
+ * by |frame|, |name| and |value| parameters are retained until
+ * `nghttp2_session_mem_recv()` or `nghttp2_session_recv()` is called.
+ * The application must retain the input bytes which was used to
+ * produce these parameters, because it may refer to the memory region
+ * included in the input bytes.
+ *
+ * Returning :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE` will close
+ * the stream by issuing RST_STREAM with
+ * :enum:`NGHTTP2_INTERNAL_ERROR`.  In this case,
+ * :type:`nghttp2_on_frame_recv_callback` will not be invoked.  If a
+ * different error code is desirable, use
+ * `nghttp2_submit_rst_stream()` with a desired error code and then
+ * return :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * It may return :enum:`NGHTTP2_ERR_PAUSE` or
+ * :enum:`NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE`.  For other critical
+ * failures, it must return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.  If
+ * the other nonzero value is returned, it is treated as
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.  If
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` is returned,
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_header_callback()`.
+ */
+typedef int (*nghttp2_on_header_callback)(nghttp2_session *session,
+                                          const nghttp2_frame *frame,
+                                          const uint8_t *name, size_t namelen,
+                                          const uint8_t *value, size_t valuelen,
+                                          uint8_t flags, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when the library asks application how
+ * many padding bytes are required for the transmission of the
+ * |frame|.  The application must choose the total length of payload
+ * including padded bytes in range [frame->hd.length, max_payloadlen],
+ * inclusive.  Choosing number not in this range will be treated as
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.  Returning
+ * ``frame->hd.length`` means no padding is added.  Returning
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` will make
+ * `nghttp2_session_send()` function immediately return
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_select_padding_callback()`.
+ */
+typedef ssize_t (*nghttp2_select_padding_callback)(nghttp2_session *session,
+                                                   const nghttp2_frame *frame,
+                                                   size_t max_payloadlen,
+                                                   void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when library wants to get max length of
+ * data to send data to the remote peer.  The implementation of this
+ * function should return a value in the following range.  [1,
+ * min(|session_remote_window_size|, |stream_remote_window_size|,
+ * |remote_max_frame_size|)].  If a value greater than this range is
+ * returned than the max allow value will be used.  Returning a value
+ * smaller than this range is treated as
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.  The |frame_type| is provided
+ * for future extensibility and identifies the type of frame (see
+ * :type:`nghttp2_frame_type`) for which to get the length for.
+ * Currently supported frame types are: :enum:`NGHTTP2_DATA`.
+ *
+ * This callback can be used to control the length in bytes for which
+ * :type:`nghttp2_data_source_read_callback` is allowed to send to the
+ * remote endpoint.  This callback is optional.  Returning
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` will signal the entire session
+ * failure.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_data_source_read_length_callback()`.
+ */
+typedef ssize_t (*nghttp2_data_source_read_length_callback)(
+    nghttp2_session *session, uint8_t frame_type, int32_t stream_id,
+    int32_t session_remote_window_size, int32_t stream_remote_window_size,
+    uint32_t remote_max_frame_size, void *user_data);
+
+/**
+ * @functypedef
+ *
+ * Callback function invoked when a frame header is received.  The
+ * |hd| points to received frame header.
+ *
+ * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will
+ * also be called when frame header of CONTINUATION frame is received.
+ *
+ * If both :type:`nghttp2_on_begin_frame_callback` and
+ * :type:`nghttp2_on_begin_headers_callback` are set and HEADERS or
+ * PUSH_PROMISE is received, :type:`nghttp2_on_begin_frame_callback`
+ * will be called first.
+ *
+ * The implementation of this function must return 0 if it succeeds.
+ * If nonzero value is returned, it is treated as fatal error and
+ * `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` functions
+ * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`.
+ *
+ * To set this callback to :type:`nghttp2_session_callbacks`, use
+ * `nghttp2_session_callbacks_set_on_begin_frame_callback()`.
+ */
+typedef int (*nghttp2_on_begin_frame_callback)(nghttp2_session *session,
+                                               const nghttp2_frame_hd *hd,
+                                               void *user_data);
+
+struct nghttp2_session_callbacks;
+
+/**
+ * @struct
+ *
+ * Callback functions for :type:`nghttp2_session`.  The details of
+ * this structure are intentionally hidden from the public API.
+ */
+typedef struct nghttp2_session_callbacks nghttp2_session_callbacks;
+
+/**
+ * @function
+ *
+ * Initializes |*callbacks_ptr| with NULL values.
+ *
+ * The initialized object can be used when initializing multiple
+ * :type:`nghttp2_session` objects.
+ *
+ * When the application finished using this object, it can use
+ * `nghttp2_session_callbacks_del()` to free its memory.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr);
+
+/**
+ * @function
+ *
+ * Frees any resources allocated for |callbacks|.  If |callbacks| is
+ * ``NULL``, this function does nothing.
+ */
+void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a session wants to send data to
+ * the remote peer.  This callback is not necessary if the application
+ * uses solely `nghttp2_session_mem_send()` to serialize data to
+ * transmit.
+ */
+void nghttp2_session_callbacks_set_send_callback(
+    nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the a session wants to receive
+ * data from the remote peer.  This callback is not necessary if the
+ * application uses solely `nghttp2_session_mem_recv()` to process
+ * received data.
+ */
+void nghttp2_session_callbacks_set_recv_callback(
+    nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked by `nghttp2_session_recv()` when a
+ * frame is received.
+ */
+void nghttp2_session_callbacks_set_on_frame_recv_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_frame_recv_callback on_frame_recv_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked by `nghttp2_session_recv()` when an
+ * invalid non-DATA frame is received.
+ */
+void nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a chunk of data in DATA frame
+ * is received.
+ */
+void nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked before a non-DATA frame is sent.
+ */
+void nghttp2_session_callbacks_set_before_frame_send_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_before_frame_send_callback before_frame_send_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked after a frame is sent.
+ */
+void nghttp2_session_callbacks_set_on_frame_send_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_frame_send_callback on_frame_send_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a non-DATA frame is not sent
+ * because of an error.
+ */
+void nghttp2_session_callbacks_set_on_frame_not_send_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_frame_not_send_callback on_frame_not_send_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the stream is closed.
+ */
+void nghttp2_session_callbacks_set_on_stream_close_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_stream_close_callback on_stream_close_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the reception of header block
+ * in HEADERS or PUSH_PROMISE is started.
+ */
+void nghttp2_session_callbacks_set_on_begin_headers_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_begin_headers_callback on_begin_headers_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a header name/value pair is
+ * received.
+ */
+void nghttp2_session_callbacks_set_on_header_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_header_callback on_header_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when the library asks application
+ * how many padding bytes are required for the transmission of the
+ * given frame.
+ */
+void nghttp2_session_callbacks_set_select_padding_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_select_padding_callback select_padding_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function determine the length allowed in
+ * :type:`nghttp2_data_source_read_callback`.
+ */
+void nghttp2_session_callbacks_set_data_source_read_length_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_data_source_read_length_callback data_source_read_length_callback);
+
+/**
+ * @function
+ *
+ * Sets callback function invoked when a frame header is received.
+ */
+void nghttp2_session_callbacks_set_on_begin_frame_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_begin_frame_callback on_begin_frame_callback);
+
+/**
+ * @functypedef
+ *
+ * Custom memory allocator to replace malloc().  The |mem_user_data|
+ * is the mem_user_data member of :type:`nghttp2_mem` structure.
+ */
+typedef void *(*nghttp2_malloc)(size_t size, void *mem_user_data);
+
+/**
+ * @functypedef
+ *
+ * Custom memory allocator to replace free().  The |mem_user_data| is
+ * the mem_user_data member of :type:`nghttp2_mem` structure.
+ */
+typedef void (*nghttp2_free)(void *ptr, void *mem_user_data);
+
+/**
+ * @functypedef
+ *
+ * Custom memory allocator to replace calloc().  The |mem_user_data|
+ * is the mem_user_data member of :type:`nghttp2_mem` structure.
+ */
+typedef void *(*nghttp2_calloc)(size_t nmemb, size_t size, void *mem_user_data);
+
+/**
+ * @functypedef
+ *
+ * Custom memory allocator to replace realloc().  The |mem_user_data|
+ * is the mem_user_data member of :type:`nghttp2_mem` structure.
+ */
+typedef void *(*nghttp2_realloc)(void *ptr, size_t size, void *mem_user_data);
+
+/**
+ * @struct
+ *
+ * Custom memory allocator functions and user defined pointer.  The
+ * |mem_user_data| member is passed to each allocator function.  This
+ * can be used, for example, to achieve per-session memory pool.
+ *
+ * In the following example code, ``my_malloc``, ``my_free``,
+ * ``my_calloc`` and ``my_realloc`` are the replacement of the
+ * standard allocators ``malloc``, ``free``, ``calloc`` and
+ * ``realloc`` respectively::
+ *
+ *     void *my_malloc_cb(size_t size, void *mem_user_data) {
+ *       return my_malloc(size);
+ *     }
+ *
+ *     void my_free_cb(void *ptr, void *mem_user_data) { my_free(ptr); }
+ *
+ *     void *my_calloc_cb(size_t nmemb, size_t size, void *mem_user_data) {
+ *       return my_calloc(nmemb, size);
+ *     }
+ *
+ *     void *my_realloc_cb(void *ptr, size_t size, void *mem_user_data) {
+ *       return my_realloc(ptr, size);
+ *     }
+ *
+ *     void session_new() {
+ *       nghttp2_session *session;
+ *       nghttp2_session_callbacks *callbacks;
+ *       nghttp2_mem mem = {NULL, my_malloc_cb, my_free_cb, my_calloc_cb,
+ *                          my_realloc_cb};
+ *
+ *       ...
+ *
+ *       nghttp2_session_client_new3(&session, callbacks, NULL, NULL, &mem);
+ *
+ *       ...
+ *     }
+ */
+typedef struct {
+  /**
+   * An arbitrary user supplied data.  This is passed to each
+   * allocator function.
+   */
+  void *mem_user_data;
+  /**
+   * Custom allocator function to replace malloc().
+   */
+  nghttp2_malloc malloc;
+  /**
+   * Custom allocator function to replace free().
+   */
+  nghttp2_free free;
+  /**
+   * Custom allocator function to replace calloc().
+   */
+  nghttp2_calloc calloc;
+  /**
+   * Custom allocator function to replace realloc().
+   */
+  nghttp2_realloc realloc;
+} nghttp2_mem;
+
+struct nghttp2_option;
+
+/**
+ * @struct
+ *
+ * Configuration options for :type:`nghttp2_session`.  The details of
+ * this structure are intentionally hidden from the public API.
+ */
+typedef struct nghttp2_option nghttp2_option;
+
+/**
+ * @function
+ *
+ * Initializes |*option_ptr| with default values.
+ *
+ * When the application finished using this object, it can use
+ * `nghttp2_option_del()` to free its memory.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_option_new(nghttp2_option **option_ptr);
+
+/**
+ * @function
+ *
+ * Frees any resources allocated for |option|.  If |option| is
+ * ``NULL``, this function does nothing.
+ */
+void nghttp2_option_del(nghttp2_option *option);
+
+/**
+ * @function
+ *
+ * This option prevents the library from sending WINDOW_UPDATE for a
+ * connection automatically.  If this option is set to nonzero, the
+ * library won't send WINDOW_UPDATE for DATA until application calls
+ * `nghttp2_session_consume()` to indicate the consumed amount of
+ * data.  Don't use `nghttp2_submit_window_update()` for this purpose.
+ * By default, this option is set to zero.
+ */
+void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val);
+
+/**
+ * @function
+ *
+ * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of
+ * remote endpoint as if it is received in SETTINGS frame.  Without
+ * specifying this option, before the local endpoint receives
+ * SETTINGS_MAX_CONCURRENT_STREAMS in SETTINGS frame from remote
+ * endpoint, SETTINGS_MAX_CONCURRENT_STREAMS is unlimited.  This may
+ * cause problem if local endpoint submits lots of requests initially
+ * and sending them at once to the remote peer may lead to the
+ * rejection of some requests.  Specifying this option to the sensible
+ * value, say 100, may avoid this kind of issue. This value will be
+ * overwritten if the local endpoint receives
+ * SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint.
+ */
+void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,
+                                                    uint32_t val);
+
+/**
+ * @function
+ *
+ * By default, nghttp2 library only handles HTTP/2 frames and does not
+ * recognize first 24 bytes of client connection preface.  This design
+ * choice is done due to the fact that server may want to detect the
+ * application protocol based on first few bytes on clear text
+ * communication.  But for simple servers which only speak HTTP/2, it
+ * is easier for developers if nghttp2 library takes care of client
+ * connection preface.
+ *
+ * If this option is used with nonzero |val|, nghttp2 library checks
+ * first 24 bytes client connection preface.  If it is not a valid
+ * one, `nghttp2_session_recv()` and `nghttp2_session_mem_recv()` will
+ * return error :enum:`NGHTTP2_ERR_BAD_PREFACE`, which is fatal error.
+ */
+void nghttp2_option_set_recv_client_preface(nghttp2_option *option, int val);
+
+/**
+ * @function
+ *
+ * Initializes |*session_ptr| for client use.  The all members of
+ * |callbacks| are copied to |*session_ptr|.  Therefore |*session_ptr|
+ * does not store |callbacks|.  The |user_data| is an arbitrary user
+ * supplied data, which will be passed to the callback functions.
+ *
+ * The :type:`nghttp2_send_callback` must be specified.  If the
+ * application code uses `nghttp2_session_recv()`, the
+ * :type:`nghttp2_recv_callback` must be specified.  The other members
+ * of |callbacks| can be ``NULL``.
+ *
+ * If this function fails, |*session_ptr| is left untouched.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_client_new(nghttp2_session **session_ptr,
+                               const nghttp2_session_callbacks *callbacks,
+                               void *user_data);
+
+/**
+ * @function
+ *
+ * Initializes |*session_ptr| for server use.  The all members of
+ * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr|
+ * does not store |callbacks|.  The |user_data| is an arbitrary user
+ * supplied data, which will be passed to the callback functions.
+ *
+ * The :type:`nghttp2_send_callback` must be specified.  If the
+ * application code uses `nghttp2_session_recv()`, the
+ * :type:`nghttp2_recv_callback` must be specified.  The other members
+ * of |callbacks| can be ``NULL``.
+ *
+ * If this function fails, |*session_ptr| is left untouched.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_server_new(nghttp2_session **session_ptr,
+                               const nghttp2_session_callbacks *callbacks,
+                               void *user_data);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_session_client_new()`, but with additional options
+ * specified in the |option|.
+ *
+ * The |option| can be ``NULL`` and the call is equivalent to
+ * `nghttp2_session_client_new()`.
+ *
+ * This function does not take ownership |option|.  The application is
+ * responsible for freeing |option| if it finishes using the object.
+ *
+ * The library code does not refer to |option| after this function
+ * returns.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_client_new2(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_session_server_new()`, but with additional options
+ * specified in the |option|.
+ *
+ * The |option| can be ``NULL`` and the call is equivalent to
+ * `nghttp2_session_server_new()`.
+ *
+ * This function does not take ownership |option|.  The application is
+ * responsible for freeing |option| if it finishes using the object.
+ *
+ * The library code does not refer to |option| after this function
+ * returns.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_server_new2(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_session_client_new2()`, but with additional custom
+ * memory allocator specified in the |mem|.
+ *
+ * The |mem| can be ``NULL`` and the call is equivalent to
+ * `nghttp2_session_client_new2()`.
+ *
+ * This function does not take ownership |mem|.  The application is
+ * responsible for freeing |mem|.
+ *
+ * The library code does not refer to |mem| pointer after this
+ * function returns, so the application can safely free it.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_client_new3(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option,
+                                nghttp2_mem *mem);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_session_server_new2()`, but with additional custom
+ * memory allocator specified in the |mem|.
+ *
+ * The |mem| can be ``NULL`` and the call is equivalent to
+ * `nghttp2_session_server_new2()`.
+ *
+ * This function does not take ownership |mem|.  The application is
+ * responsible for freeing |mem|.
+ *
+ * The library code does not refer to |mem| pointer after this
+ * function returns, so the application can safely free it.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_server_new3(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option,
+                                nghttp2_mem *mem);
+
+/**
+ * @function
+ *
+ * Frees any resources allocated for |session|.  If |session| is
+ * ``NULL``, this function does nothing.
+ */
+void nghttp2_session_del(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Sends pending frames to the remote peer.
+ *
+ * This function retrieves the highest prioritized frame from the
+ * outbound queue and sends it to the remote peer.  It does this as
+ * many as possible until the user callback
+ * :type:`nghttp2_send_callback` returns
+ * :enum:`NGHTTP2_ERR_WOULDBLOCK` or the outbound queue becomes empty.
+ * This function calls several callback functions which are passed
+ * when initializing the |session|.  Here is the simple time chart
+ * which tells when each callback is invoked:
+ *
+ * 1. Get the next frame to send from outbound queue.
+ *
+ * 2. Prepare transmission of the frame.
+ *
+ * 3. If the control frame cannot be sent because some preconditions
+ *    are not met (e.g., request HEADERS cannot be sent after GOAWAY),
+ *    :type:`nghttp2_on_frame_not_send_callback` is invoked.  Abort
+ *    the following steps.
+ *
+ * 4. If the frame is HEADERS, PUSH_PROMISE or DATA,
+ *    :type:`nghttp2_select_padding_callback` is invoked.
+ *
+ * 5. If the frame is request HEADERS, the stream is opened here.
+ *
+ * 6. :type:`nghttp2_before_frame_send_callback` is invoked.
+ *
+ * 7. :type:`nghttp2_send_callback` is invoked one or more times to
+ *    send the frame.
+ *
+ * 8. :type:`nghttp2_on_frame_send_callback` is invoked.
+ *
+ * 9. If the transmission of the frame triggers closure of the stream,
+ *    the stream is closed and
+ *    :type:`nghttp2_on_stream_close_callback` is invoked.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`
+ *     The callback function failed.
+ */
+int nghttp2_session_send(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the serialized data to send.
+ *
+ * This function behaves like `nghttp2_session_send()` except that it
+ * does not use :type:`nghttp2_send_callback` to transmit data.
+ * Instead, it assigns the pointer to the serialized data to the
+ * |*data_ptr| and returns its length.  The other callbacks are called
+ * in the same way as they are in `nghttp2_session_send()`.
+ *
+ * If no data is available to send, this function returns 0.
+ *
+ * This function may not return all serialized data in one invocation.
+ * To get all data, call this function repeatedly until it returns 0
+ * or one of negative error codes.
+ *
+ * The assigned |*data_ptr| is valid until the next call of
+ * `nghttp2_session_mem_send()` or `nghttp2_session_send()`.
+ *
+ * The caller must send all data before sending the next chunk of
+ * data.
+ *
+ * This function returns the length of the data pointed by the
+ * |*data_ptr| if it succeeds, or one of the following negative error
+ * codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+ssize_t nghttp2_session_mem_send(nghttp2_session *session,
+                                 const uint8_t **data_ptr);
+
+/**
+ * @function
+ *
+ * Receives frames from the remote peer.
+ *
+ * This function receives as many frames as possible until the user
+ * callback :type:`nghttp2_recv_callback` returns
+ * :enum:`NGHTTP2_ERR_WOULDBLOCK`.  This function calls several
+ * callback functions which are passed when initializing the
+ * |session|.  Here is the simple time chart which tells when each
+ * callback is invoked:
+ *
+ * 1. :type:`nghttp2_recv_callback` is invoked one or more times to
+ *    receive frame header.
+ *
+ * 2. When frame header is received,
+ *    :type:`nghttp2_on_begin_frame_callback` is invoked.
+ *
+ * 3. If the frame is DATA frame:
+ *
+ *    1. :type:`nghttp2_recv_callback` is invoked to receive DATA
+ *       payload. For each chunk of data,
+ *       :type:`nghttp2_on_data_chunk_recv_callback` is invoked.
+ *
+ *    2. If one DATA frame is completely received,
+ *       :type:`nghttp2_on_frame_recv_callback` is invoked.  If the
+ *       reception of the frame triggers the closure of the stream,
+ *       :type:`nghttp2_on_stream_close_callback` is invoked.
+ *
+ * 4. If the frame is the control frame:
+ *
+ *    1. :type:`nghttp2_recv_callback` is invoked one or more times to
+ *       receive whole frame.
+ *
+ *    2. If the received frame is valid, then following actions are
+ *       taken.  If the frame is either HEADERS or PUSH_PROMISE,
+ *       :type:`nghttp2_on_begin_headers_callback` is invoked.  Then
+ *       :type:`nghttp2_on_header_callback` is invoked for each header
+ *       name/value pair.  After all name/value pairs are emitted
+ *       successfully, :type:`nghttp2_on_frame_recv_callback` is
+ *       invoked.  For other frames,
+ *       :type:`nghttp2_on_frame_recv_callback` is invoked.  If the
+ *       reception of the frame triggers the closure of the stream,
+ *       :type:`nghttp2_on_stream_close_callback` is invoked.
+ *
+ *    3. If the received frame is unpacked but is interpreted as
+ *       invalid, :type:`nghttp2_on_invalid_frame_recv_callback` is
+ *       invoked.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_EOF`
+ *     The remote peer did shutdown on the connection.
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`
+ *     The callback function failed.
+ * :enum:`NGHTTP2_ERR_BAD_PREFACE`
+ *     Invalid client preface was detected.  This error only returns
+ *     when |session| was configured as server and
+ *     `nghttp2_option_set_recv_client_preface()` is used.
+ */
+int nghttp2_session_recv(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Processes data |in| as an input from the remote endpoint.  The
+ * |inlen| indicates the number of bytes in the |in|.
+ *
+ * This function behaves like `nghttp2_session_recv()` except that it
+ * does not use :type:`nghttp2_recv_callback` to receive data; the
+ * |in| is the only data for the invocation of this function.  If all
+ * bytes are processed, this function returns.  The other callbacks
+ * are called in the same way as they are in `nghttp2_session_recv()`.
+ *
+ * In the current implementation, this function always tries to
+ * processes all input data unless either an error occurs or
+ * :enum:`NGHTTP2_ERR_PAUSE` is returned from
+ * :type:`nghttp2_on_header_callback` or
+ * :type:`nghttp2_on_data_chunk_recv_callback`.  If
+ * :enum:`NGHTTP2_ERR_PAUSE` is used, the return value includes the
+ * number of bytes which was used to produce the data or frame for the
+ * callback.
+ *
+ * This function returns the number of processed bytes, or one of the
+ * following negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`
+ *     The callback function failed.
+ * :enum:`NGHTTP2_ERR_BAD_PREFACE`
+ *     Invalid client preface was detected.  This error only returns
+ *     when |session| was configured as server and
+ *     `nghttp2_option_set_recv_client_preface()` is used.
+ */
+ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
+                                 size_t inlen);
+
+/**
+ * @function
+ *
+ * Puts back previously deferred DATA frame in the stream |stream_id|
+ * to the outbound queue.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The stream does not exist; or no deferred data exist.
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns nonzero value if |session| wants to receive data from the
+ * remote peer.
+ *
+ * If both `nghttp2_session_want_read()` and
+ * `nghttp2_session_want_write()` return 0, the application should
+ * drop the connection.
+ */
+int nghttp2_session_want_read(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns nonzero value if |session| wants to send data to the remote
+ * peer.
+ *
+ * If both `nghttp2_session_want_read()` and
+ * `nghttp2_session_want_write()` return 0, the application should
+ * drop the connection.
+ */
+int nghttp2_session_want_write(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns stream_user_data for the stream |stream_id|.  The
+ * stream_user_data is provided by `nghttp2_submit_request()`,
+ * `nghttp2_submit_headers()` or
+ * `nghttp2_session_set_stream_user_data()`.  Unless it is set using
+ * `nghttp2_session_set_stream_user_data()`, if the stream is
+ * initiated by the remote endpoint, stream_user_data is always
+ * ``NULL``.  If the stream does not exist, this function returns
+ * ``NULL``.
+ */
+void *nghttp2_session_get_stream_user_data(nghttp2_session *session,
+                                           int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Sets the |stream_user_data| to the stream denoted by the
+ * |stream_id|.  If a stream user data is already set to the stream,
+ * it is replaced with the |stream_user_data|.  It is valid to specify
+ * ``NULL`` in the |stream_user_data|, which nullifies the associated
+ * data pointer.
+ *
+ * It is valid to set the |stream_user_data| to the stream reserved by
+ * PUSH_PROMISE frame.
+ *
+ * This function returns 0 if it succeeds, or one of following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The stream does not exist
+ */
+int nghttp2_session_set_stream_user_data(nghttp2_session *session,
+                                         int32_t stream_id,
+                                         void *stream_user_data);
+
+/**
+ * @function
+ *
+ * Returns the number of frames in the outbound queue.  This does not
+ * include the deferred DATA frames.
+ */
+size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the number of DATA payload in bytes received without
+ * WINDOW_UPDATE transmission for the stream |stream_id|.  The local
+ * (receive) window size can be adjusted by
+ * `nghttp2_submit_window_update()`.  This function takes into account
+ * that and returns effective data length.  In particular, if the
+ * local window size is reduced by submitting negative
+ * window_size_increment with `nghttp2_submit_window_update()`, this
+ * function returns the number of bytes less than actually received.
+ *
+ * This function returns -1 if it fails.
+ */
+int32_t
+nghttp2_session_get_stream_effective_recv_data_length(nghttp2_session *session,
+                                                      int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns the local (receive) window size for the stream |stream_id|.
+ * The local window size can be adjusted by
+ * `nghttp2_submit_window_update()`.  This function takes into account
+ * that and returns effective window size.
+ *
+ * This function returns -1 if it fails.
+ */
+int32_t
+nghttp2_session_get_stream_effective_local_window_size(nghttp2_session *session,
+                                                       int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns the number of DATA payload in bytes received without
+ * WINDOW_UPDATE transmission for a connection.  The local (receive)
+ * window size can be adjusted by `nghttp2_submit_window_update()`.
+ * This function takes into account that and returns effective data
+ * length.  In particular, if the local window size is reduced by
+ * submitting negative window_size_increment with
+ * `nghttp2_submit_window_update()`, this function returns the number
+ * of bytes less than actually received.
+ *
+ * This function returns -1 if it fails.
+ */
+int32_t
+nghttp2_session_get_effective_recv_data_length(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the local (receive) window size for a connection.  The
+ * local window size can be adjusted by
+ * `nghttp2_submit_window_update()`.  This function takes into account
+ * that and returns effective window size.
+ *
+ * This function returns -1 if it fails.
+ */
+int32_t
+nghttp2_session_get_effective_local_window_size(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the remote window size for a given stream |stream_id|.
+ *
+ * This is the amount of flow-controlled payload (e.g., DATA) that the
+ * local endpoint can send without stream level WINDOW_UPDATE.  There
+ * is also connection level flow control, so the effective size of
+ * payload that the local endpoint can actually send is
+ * min(`nghttp2_session_get_stream_remote_window_size()`,
+ * `nghttp2_session_get_remote_window_size()`).
+ *
+ * This function returns -1 if it fails.
+ */
+int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session *session,
+                                                      int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns the remote window size for a connection.
+ *
+ * This function always succeeds.
+ */
+int32_t nghttp2_session_get_remote_window_size(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns 1 if local peer half closed the given stream |stream_id|.
+ * Returns 0 if it did not.  Returns -1 if no such stream exists.
+ */
+int nghttp2_session_get_stream_local_close(nghttp2_session *session,
+                                           int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Returns 1 if remote peer half closed the given stream |stream_id|.
+ * Returns 0 if it did not.  Returns -1 if no such stream exists.
+ */
+int nghttp2_session_get_stream_remote_close(nghttp2_session *session,
+                                            int32_t stream_id);
+
+/**
+ * @function
+ *
+ * Signals the session so that the connection should be terminated.
+ *
+ * The last stream ID is the minimum value between the stream ID of a
+ * stream for which :type:`nghttp2_on_frame_recv_callback` was called
+ * most recently and the last stream ID we have sent to the peer
+ * previously.
+ *
+ * The |error_code| is the error code of this GOAWAY frame.  The
+ * pre-defined error code is one of :enum:`nghttp2_error_code`.
+ *
+ * After the transmission, both `nghttp2_session_want_read()` and
+ * `nghttp2_session_want_write()` return 0.
+ *
+ * This function should be called when the connection should be
+ * terminated after sending GOAWAY.  If the remaining streams should
+ * be processed after GOAWAY, use `nghttp2_submit_goaway()` instead.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_session_terminate_session(nghttp2_session *session,
+                                      uint32_t error_code);
+
+/**
+ * @function
+ *
+ * Signals the session so that the connection should be terminated.
+ *
+ * This function behaves like `nghttp2_session_terminate_session()`,
+ * but the last stream ID can be specified by the application for fine
+ * grained control of stream.  The HTTP/2 specification does not allow
+ * last_stream_id to be increased.  So the actual value sent as
+ * last_stream_id is the minimum value between the given
+ * |last_stream_id| and the last_stream_id we have previously sent to
+ * the peer.
+ *
+ * The |last_stream_id| is peer's stream ID or 0.  So if |session| is
+ * initialized as client, |last_stream_id| must be even or 0.  If
+ * |session| is initialized as server, |last_stream_id| must be odd or
+ * 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |last_stream_id| is invalid.
+ */
+int nghttp2_session_terminate_session2(nghttp2_session *session,
+                                       int32_t last_stream_id,
+                                       uint32_t error_code);
+
+/**
+ * @function
+ *
+ * Signals to the client that the server started graceful shutdown
+ * procedure.
+ *
+ * This function is only usable for server.  If this function is
+ * called with client side session, this function returns
+ * :enum:`NGHTTP2_ERR_INVALID_STATE`.
+ *
+ * To gracefully shutdown HTTP/2 session, server should call this
+ * function to send GOAWAY with last_stream_id (1u << 31) - 1.  And
+ * after some delay (e.g., 1 RTT), send another GOAWAY with the stream
+ * ID that the server has some processing using
+ * `nghttp2_submit_goaway()`.  See also
+ * `nghttp2_session_get_last_proc_stream_id()`.
+ *
+ * Unlike `nghttp2_submit_goaway()`, this function just sends GOAWAY
+ * and does nothing more.  This is a mere indication to the client
+ * that session shutdown is imminent.  The application should call
+ * `nghttp2_submit_goaway()` with appropriate last_stream_id after
+ * this call.
+ *
+ * If one or more GOAWAY frame have been already sent by either
+ * `nghttp2_submit_goaway()` or `nghttp2_session_terminate_session()`,
+ * this function has no effect.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_STATE`
+ *     The |session| is initialized as client.
+ */
+int nghttp2_submit_shutdown_notice(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Returns the value of SETTINGS |id| notified by a remote endpoint.
+ * The |id| must be one of values defined in
+ * :enum:`nghttp2_settings_id`.
+ */
+uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
+                                             nghttp2_settings_id id);
+
+/**
+ * @function
+ *
+ * Tells the |session| that next stream ID is |next_stream_id|.  The
+ * |next_stream_id| must be equal or greater than the value returned
+ * by `nghttp2_session_get_next_stream_id()`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |next_stream_id| is strictly less than the value
+ *     `nghttp2_session_get_next_stream_id()` returns.
+ */
+int nghttp2_session_set_next_stream_id(nghttp2_session *session,
+                                       int32_t next_stream_id);
+
+/**
+ * @function
+ *
+ * Returns the next outgoing stream ID.  Notice that return type is
+ * uint32_t.  If we run out of stream ID for this session, this
+ * function returns 1 << 31.
+ */
+uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Tells the |session| that |size| bytes for a stream denoted by
+ * |stream_id| were consumed by application and are ready to
+ * WINDOW_UPDATE.  This function is intended to be used without
+ * automatic window update (see
+ * `nghttp2_option_set_no_auto_window_update()`).
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0.
+ * :enum:`NGHTTP2_ERR_INVALID_STATE`
+ *     Automatic WINDOW_UPDATE is not disabled.
+ */
+int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id,
+                            size_t size);
+
+/**
+ * @function
+ *
+ * Performs post-process of HTTP Upgrade request.  This function can
+ * be called from both client and server, but the behavior is very
+ * different in each other.
+ *
+ * If called from client side, the |settings_payload| must be the
+ * value sent in ``HTTP2-Settings`` header field and must be decoded
+ * by base64url decoder.  The |settings_payloadlen| is the length of
+ * |settings_payload|.  The |settings_payload| is unpacked and its
+ * setting values will be submitted using `nghttp2_submit_settings()`.
+ * This means that the client application code does not need to submit
+ * SETTINGS by itself.  The stream with stream ID=1 is opened and the
+ * |stream_user_data| is used for its stream_user_data.  The opened
+ * stream becomes half-closed (local) state.
+ *
+ * If called from server side, the |settings_payload| must be the
+ * value received in ``HTTP2-Settings`` header field and must be
+ * decoded by base64url decoder.  The |settings_payloadlen| is the
+ * length of |settings_payload|.  It is treated as if the SETTINGS
+ * frame with that payload is received.  Thus, callback functions for
+ * the reception of SETTINGS frame will be invoked.  The stream with
+ * stream ID=1 is opened.  The |stream_user_data| is ignored.  The
+ * opened stream becomes half-closed (remote).
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |settings_payload| is badly formed.
+ * :enum:`NGHTTP2_ERR_PROTO`
+ *     The stream ID 1 is already used or closed; or is not available.
+ */
+int nghttp2_session_upgrade(nghttp2_session *session,
+                            const uint8_t *settings_payload,
+                            size_t settings_payloadlen, void *stream_user_data);
+
+/**
+ * @function
+ *
+ * Serializes the SETTINGS values |iv| in the |buf|.  The size of the
+ * |buf| is specified by |buflen|.  The number of entries in the |iv|
+ * array is given by |niv|.  The required space in |buf| for the |niv|
+ * entries is ``8*niv`` bytes and if the given buffer is too small, an
+ * error is returned.  This function is used mainly for creating a
+ * SETTINGS payload to be sent with the ``HTTP2-Settings`` header
+ * field in an HTTP Upgrade request.  The data written in |buf| is NOT
+ * base64url encoded and the application is responsible for encoding.
+ *
+ * This function returns the number of bytes written in |buf|, or one
+ * of the following negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |iv| contains duplicate settings ID or invalid value.
+ *
+ * :enum:`NGHTTP2_ERR_INSUFF_BUFSIZE`
+ *     The provided |buflen| size is too small to hold the output.
+ */
+ssize_t nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen,
+                                      const nghttp2_settings_entry *iv,
+                                      size_t niv);
+
+/**
+ * @function
+ *
+ * Returns string describing the |lib_error_code|.  The
+ * |lib_error_code| must be one of the :enum:`nghttp2_error`.
+ */
+const char *nghttp2_strerror(int lib_error_code);
+
+/**
+ * @function
+ *
+ * Initializes |pri_spec| with the |stream_id| of the stream to depend
+ * on with |weight| and its exclusive flag.  If |exclusive| is
+ * nonzero, exclusive flag is set.
+ *
+ * The |weight| must be in [:enum:`NGHTTP2_MIN_WEIGHT`,
+ * :enum:`NGHTTP2_MAX_WEIGHT`], inclusive.
+ */
+void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec,
+                                int32_t stream_id, int32_t weight,
+                                int exclusive);
+
+/**
+ * @function
+ *
+ * Initializes |pri_spec| with the default values.  The default values
+ * are: stream_id = 0, weight = :macro:`NGHTTP2_DEFAULT_WEIGHT` and
+ * exclusive = 0.
+ */
+void nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the |pri_spec| is filled with default values.
+ */
+int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
+
+/**
+ * @function
+ *
+ * Submits HEADERS frame and optionally one or more DATA frames.
+ *
+ * The |pri_spec| is priority specification of this request.  ``NULL``
+ * means the default priority (see
+ * `nghttp2_priority_spec_default_init()`).  To specify the priority,
+ * use `nghttp2_priority_spec_init()`.  If |pri_spec| is not ``NULL``,
+ * this function will copy its data members.
+ *
+ * The `pri_spec->weight` must be in [:enum:`NGHTTP2_MIN_WEIGHT`,
+ * :enum:`NGHTTP2_MAX_WEIGHT`], inclusive.  If `pri_spec->weight` is
+ * strictly less than :enum:`NGHTTP2_MIN_WEIGHT`, it becomes
+ * :enum:`NGHTTP2_MIN_WEIGHT`.  If it is strictly greater than
+ * :enum:`NGHTTP2_MAX_WEIGHT`, it becomes :enum:`NGHTTP2_MAX_WEIGHT`.
+ *
+ * The |nva| is an array of name/value pair :type:`nghttp2_nv` with
+ * |nvlen| elements.  The application is responsible to include
+ * required pseudo-header fields (header field whose name starts with
+ * ":") in |nva| and must place pseudo-headers before regular header
+ * fields.
+ *
+ * This function creates copies of all name/value pairs in |nva|.  It
+ * also lower-cases all names in |nva|.  The order of elements in
+ * |nva| is preserved.
+ *
+ * HTTP/2 specification has requirement about header fields in the
+ * request HEADERS.  See the specification for more details.
+ *
+ * If |data_prd| is not ``NULL``, it provides data which will be sent
+ * in subsequent DATA frames.  In this case, a method that allows
+ * request message bodies
+ * (http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9) must
+ * be specified with ``:method`` key in |nva| (e.g. ``POST``).  This
+ * function does not take ownership of the |data_prd|.  The function
+ * copies the members of the |data_prd|.  If |data_prd| is ``NULL``,
+ * HEADERS have END_STREAM set.  The |stream_user_data| is data
+ * associated to the stream opened by this request and can be an
+ * arbitrary pointer, which can be retrieved later by
+ * `nghttp2_session_get_stream_user_data()`.
+ *
+ * This function returns assigned stream ID if it succeeds, or one of
+ * the following negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
+ *     No stream ID is available because maximum stream ID was
+ *     reached.
+ *
+ * .. warning::
+ *
+ *   This function returns assigned stream ID if it succeeds.  But
+ *   that stream is not opened yet.  The application must not submit
+ *   frame to that stream ID before
+ *   :type:`nghttp2_before_frame_send_callback` is called for this
+ *   frame.
+ *
+ */
+int32_t nghttp2_submit_request(nghttp2_session *session,
+                               const nghttp2_priority_spec *pri_spec,
+                               const nghttp2_nv *nva, size_t nvlen,
+                               const nghttp2_data_provider *data_prd,
+                               void *stream_user_data);
+
+/**
+ * @function
+ *
+ * Submits response HEADERS frame and optionally one or more DATA
+ * frames against the stream |stream_id|.
+ *
+ * The |nva| is an array of name/value pair :type:`nghttp2_nv` with
+ * |nvlen| elements.  The application is responsible to include
+ * required pseudo-header fields (header field whose name starts with
+ * ":") in |nva| and must place pseudo-headers before regular header
+ * fields.
+ *
+ * This function creates copies of all name/value pairs in |nva|.  It
+ * also lower-cases all names in |nva|.  The order of elements in
+ * |nva| is preserved.
+ *
+ * HTTP/2 specification has requirement about header fields in the
+ * response HEADERS.  See the specification for more details.
+ *
+ * If |data_prd| is not ``NULL``, it provides data which will be sent
+ * in subsequent DATA frames.  This function does not take ownership
+ * of the |data_prd|.  The function copies the members of the
+ * |data_prd|.  If |data_prd| is ``NULL``, HEADERS will have
+ * END_STREAM flag set.
+ *
+ * This method can be used as normal HTTP response and push response.
+ * When pushing a resource using this function, the |session| must be
+ * configured using `nghttp2_session_server_new()` or its variants and
+ * the target stream denoted by the |stream_id| must be reserved using
+ * `nghttp2_submit_push_promise()`.
+ *
+ * To send non-final response headers (e.g., HTTP status 101), don't
+ * use this function because this function half-closes the outbound
+ * stream.  Instead, use `nghttp2_submit_headers()` for this purpose.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0.
+ *
+ * .. warning::
+ *
+ *   Calling this function twice for the same stream ID may lead to
+ *   program crash.  It is generally considered to a programming error
+ *   to commit response twice.
+ */
+int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
+                            const nghttp2_nv *nva, size_t nvlen,
+                            const nghttp2_data_provider *data_prd);
+
+/**
+ * @function
+ *
+ * Submits HEADERS frame. The |flags| is bitwise OR of the
+ * following values:
+ *
+ * * :enum:`NGHTTP2_FLAG_END_STREAM`
+ *
+ * If |flags| includes :enum:`NGHTTP2_FLAG_END_STREAM`, this frame has
+ * END_STREAM flag set.
+ *
+ * The library handles the CONTINUATION frame internally and it
+ * correctly sets END_HEADERS to the last sequence of the PUSH_PROMISE
+ * or CONTINUATION frame.
+ *
+ * If the |stream_id| is -1, this frame is assumed as request (i.e.,
+ * request HEADERS frame which opens new stream).  In this case, the
+ * assigned stream ID will be returned.  Otherwise, specify stream ID
+ * in |stream_id|.
+ *
+ * The |pri_spec| is priority specification of this request.  ``NULL``
+ * means the default priority (see
+ * `nghttp2_priority_spec_default_init()`).  To specify the priority,
+ * use `nghttp2_priority_spec_init()`.  If |pri_spec| is not ``NULL``,
+ * this function will copy its data members.
+ *
+ * The `pri_spec->weight` must be in [:enum:`NGHTTP2_MIN_WEIGHT`,
+ * :enum:`NGHTTP2_MAX_WEIGHT`], inclusive.  If `pri_spec->weight` is
+ * strictly less than :enum:`NGHTTP2_MIN_WEIGHT`, it becomes
+ * :enum:`NGHTTP2_MIN_WEIGHT`.  If it is strictly greater than
+ * :enum:`NGHTTP2_MAX_WEIGHT`, it becomes :enum:`NGHTTP2_MAX_WEIGHT`.
+ *
+ * The |nva| is an array of name/value pair :type:`nghttp2_nv` with
+ * |nvlen| elements.  The application is responsible to include
+ * required pseudo-header fields (header field whose name starts with
+ * ":") in |nva| and must place pseudo-headers before regular header
+ * fields.
+ *
+ * This function creates copies of all name/value pairs in |nva|.  It
+ * also lower-cases all names in |nva|.  The order of elements in
+ * |nva| is preserved.
+ *
+ * The |stream_user_data| is a pointer to an arbitrary data which is
+ * associated to the stream this frame will open.  Therefore it is
+ * only used if this frame opens streams, in other words, it changes
+ * stream state from idle or reserved to open.
+ *
+ * This function is low-level in a sense that the application code can
+ * specify flags directly.  For usual HTTP request,
+ * `nghttp2_submit_request()` is useful.
+ *
+ * This function returns newly assigned stream ID if it succeeds and
+ * |stream_id| is -1.  Otherwise, this function returns 0 if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
+ *     No stream ID is available because maximum stream ID was
+ *     reached.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0.
+ *
+ * .. warning::
+ *
+ *   This function returns assigned stream ID if it succeeds and
+ *   |stream_id| is -1.  But that stream is not opened yet.  The
+ *   application must not submit frame to that stream ID before
+ *   :type:`nghttp2_before_frame_send_callback` is called for this
+ *   frame.
+ *
+ */
+int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
+                               int32_t stream_id,
+                               const nghttp2_priority_spec *pri_spec,
+                               const nghttp2_nv *nva, size_t nvlen,
+                               void *stream_user_data);
+
+/**
+ * @function
+ *
+ * Submits one or more DATA frames to the stream |stream_id|.  The
+ * data to be sent are provided by |data_prd|.  If |flags| contains
+ * :enum:`NGHTTP2_FLAG_END_STREAM`, the last DATA frame has END_STREAM
+ * flag set.
+ *
+ * This function does not take ownership of the |data_prd|.  The
+ * function copies the members of the |data_prd|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_DATA_EXIST`
+ *     DATA has been already submitted and not fully processed yet.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0.
+ * :enum:`NGHTTP2_ERR_STREAM_CLOSED`
+ *     The stream was alreay closed; or the |stream_id| is invalid.
+ *
+ * .. note::
+ *
+ *   Currently, only one data is allowed for a stream at a time.
+ *   Submitting data more than once before first data is finished
+ *   results in :enum:`NGHTTP2_ERR_DATA_EXIST` error code.  The
+ *   earliest callback which tells that previous data is done is
+ *   :type:`nghttp2_on_frame_send_callback`.  In side that callback,
+ *   new data can be submitted using `nghttp2_submit_data()`.  Of
+ *   course, all data except for last one must not have
+ *   :enum:`NGHTTP2_FLAG_END_STREAM` flag set in |flags|.
+ */
+int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
+                        int32_t stream_id,
+                        const nghttp2_data_provider *data_prd);
+
+/**
+ * @function
+ *
+ * Submits PRIORITY frame to change the priority of stream |stream_id|
+ * to the priority specification |pri_spec|.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`NGHTTP2_FLAG_NONE`.
+ *
+ * The |pri_spec| is priority specification of this request.  ``NULL``
+ * is not allowed for this function. To specify the priority, use
+ * `nghttp2_priority_spec_init()`.  This function will copy its data
+ * members.
+ *
+ * The `pri_spec->weight` must be in [:enum:`NGHTTP2_MIN_WEIGHT`,
+ * :enum:`NGHTTP2_MAX_WEIGHT`], inclusive.  If `pri_spec->weight` is
+ * strictly less than :enum:`NGHTTP2_MIN_WEIGHT`, it becomes
+ * :enum:`NGHTTP2_MIN_WEIGHT`.  If it is strictly greater than
+ * :enum:`NGHTTP2_MAX_WEIGHT`, it becomes :enum:`NGHTTP2_MAX_WEIGHT`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0; or the |pri_spec| is NULL; or trying to
+ *     depend on itself.
+ */
+int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags,
+                            int32_t stream_id,
+                            const nghttp2_priority_spec *pri_spec);
+
+/**
+ * @function
+ *
+ * Submits RST_STREAM frame to cancel/reject the stream |stream_id|
+ * with the error code |error_code|.
+ *
+ * The pre-defined error code is one of :enum:`nghttp2_error_code`.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`NGHTTP2_FLAG_NONE`.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0.
+ */
+int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags,
+                              int32_t stream_id, uint32_t error_code);
+
+/**
+ * @function
+ *
+ * Stores local settings and submits SETTINGS frame.  The |iv| is the
+ * pointer to the array of :type:`nghttp2_settings_entry`.  The |niv|
+ * indicates the number of :type:`nghttp2_settings_entry`.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`NGHTTP2_FLAG_NONE`.
+ *
+ * This function does not take ownership of the |iv|.  This function
+ * copies all the elements in the |iv|.
+ *
+ * While updating individual stream's local window size, if the window
+ * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE,
+ * RST_STREAM is issued against such a stream.
+ *
+ * SETTINGS with :enum:`NGHTTP2_FLAG_ACK` is automatically submitted
+ * by the library and application could not send it at its will.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |iv| contains invalid value (e.g., initial window size
+ *     strictly greater than (1 << 31) - 1.
+ * :enum:`NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS`
+ *     There is already another in-flight SETTINGS.  Note that the
+ *     current implementation only allows 1 in-flight SETTINGS frame
+ *     without ACK flag set.
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags,
+                            const nghttp2_settings_entry *iv, size_t niv);
+
+/**
+ * @function
+ *
+ * Submits PUSH_PROMISE frame.
+ *
+ * The |flags| is currently ignored.  The library handles the
+ * CONTINUATION frame internally and it correctly sets END_HEADERS to
+ * the last sequence of the PUSH_PROMISE or CONTINUATION frame.
+ *
+ * The |stream_id| must be client initiated stream ID.
+ *
+ * The |nva| is an array of name/value pair :type:`nghttp2_nv` with
+ * |nvlen| elements.  The application is responsible to include
+ * required pseudo-header fields (header field whose name starts with
+ * ":") in |nva| and must place pseudo-headers before regular header
+ * fields.
+ *
+ * This function creates copies of all name/value pairs in |nva|.  It
+ * also lower-cases all names in |nva|.  The order of elements in
+ * |nva| is preserved.
+ *
+ * The |promised_stream_user_data| is a pointer to an arbitrary data
+ * which is associated to the promised stream this frame will open and
+ * make it in reserved state.  It is available using
+ * `nghttp2_session_get_stream_user_data()`.  The application can
+ * access it in :type:`nghttp2_before_frame_send_callback` and
+ * :type:`nghttp2_on_frame_send_callback` of this frame.
+ *
+ * The client side is not allowed to use this function.
+ *
+ * This function returns assigned promised stream ID if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_PROTO`
+ *     This function was invoked when |session| is initialized as
+ *     client.
+ * :enum:`NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE`
+ *     No stream ID is available because maximum stream ID was
+ *     reached.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |stream_id| is 0; The |stream_id| does not designate stream
+ *     that peer initiated.
+ *
+ * .. warning::
+ *
+ *   This function returns assigned promised stream ID if it succeeds.
+ *   But that stream is not opened yet.  The application must not
+ *   submit frame to that stream ID before
+ *   :type:`nghttp2_before_frame_send_callback` is called for this
+ *   frame.
+ *
+ */
+int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
+                                    int32_t stream_id, const nghttp2_nv *nva,
+                                    size_t nvlen,
+                                    void *promised_stream_user_data);
+
+/**
+ * @function
+ *
+ * Submits PING frame.  You don't have to send PING back when you
+ * received PING frame.  The library automatically submits PING frame
+ * in this case.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`NGHTTP2_FLAG_NONE`.
+ *
+ * If the |opaque_data| is non ``NULL``, then it should point to the 8
+ * bytes array of memory to specify opaque data to send with PING
+ * frame.  If the |opaque_data| is ``NULL``, zero-cleared 8 bytes will
+ * be sent as opaque data.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags,
+                        const uint8_t *opaque_data);
+
+/**
+ * @function
+ *
+ * Submits GOAWAY frame with the last stream ID |last_stream_id| and
+ * the error code |error_code|.
+ *
+ * The pre-defined error code is one of :enum:`nghttp2_error_code`.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`NGHTTP2_FLAG_NONE`.
+ *
+ * The |last_stream_id| is peer's stream ID or 0.  So if |session| is
+ * initialized as client, |last_stream_id| must be even or 0.  If
+ * |session| is initialized as server, |last_stream_id| must be odd or
+ * 0.
+ *
+ * The HTTP/2 specification says last_stream_id must not be increased
+ * from the value previously sent.  So the actual value sent as
+ * last_stream_id is the minimum value between the given
+ * |last_stream_id| and the last_stream_id previously sent to the
+ * peer.
+ *
+ * If the |opaque_data| is not ``NULL`` and |opaque_data_len| is not
+ * zero, those data will be sent as additional debug data.  The
+ * library makes a copy of the memory region pointed by |opaque_data|
+ * with the length |opaque_data_len|, so the caller does not need to
+ * keep this memory after the return of this function.  If the
+ * |opaque_data_len| is 0, the |opaque_data| could be ``NULL``.
+ *
+ * After successful transmission of GOAWAY, following things happen.
+ * All incoming streams having strictly more than |last_stream_id| are
+ * closed.  All incoming HEADERS which starts new stream are simply
+ * ignored.  After all active streams are handled, both
+ * `nghttp2_session_want_read()` and `nghttp2_session_want_write()`
+ * return 0 and the application can close session.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
+ *     The |opaque_data_len| is too large; the |last_stream_id| is
+ *     invalid.
+ */
+int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags,
+                          int32_t last_stream_id, uint32_t error_code,
+                          const uint8_t *opaque_data, size_t opaque_data_len);
+
+/**
+ * @function
+ *
+ * Returns the last stream ID of a stream for which
+ * :type:`nghttp2_on_frame_recv_callback` was invoked most recently.
+ * The returned value can be used as last_stream_id parameter for
+ * `nghttp2_submit_goaway()` and
+ * `nghttp2_session_terminate_session2()`.
+ *
+ * This function always succeeds.
+ */
+int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session);
+
+/**
+ * @function
+ *
+ * Submits WINDOW_UPDATE frame.
+ *
+ * The |flags| is currently ignored and should be
+ * :enum:`NGHTTP2_FLAG_NONE`.
+ *
+ * If the |window_size_increment| is positive, the WINDOW_UPDATE with
+ * that value as window_size_increment is queued.  If the
+ * |window_size_increment| is larger than the received bytes from the
+ * remote endpoint, the local window size is increased by that
+ * difference.
+ *
+ * If the |window_size_increment| is negative, the local window size
+ * is decreased by -|window_size_increment|.  If automatic
+ * WINDOW_UPDATE is enabled
+ * (`nghttp2_option_set_no_auto_window_update()`), and the library
+ * decided that the WINDOW_UPDATE should be submitted, then
+ * WINDOW_UPDATE is queued with the current received bytes count.
+ *
+ * If the |window_size_increment| is 0, the function does nothing and
+ * returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_FLOW_CONTROL`
+ *     The local window size overflow or gets negative.
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
+                                 int32_t stream_id,
+                                 int32_t window_size_increment);
+
+/**
+ * @function
+ *
+ * This function previously submits ALTSVC frame with given
+ * parameters, but is deprecated and will be removed in a future
+ * release.  This function does nothing and just return 0.
+ */
+int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags,
+                          int32_t stream_id, uint32_t max_age, uint16_t port,
+                          const uint8_t *protocol_id, size_t protocol_id_len,
+                          const uint8_t *host, size_t host_len,
+                          const uint8_t *origin, size_t origin_len);
+
+/**
+ * @function
+ *
+ * Compares ``lhs->name`` of length ``lhs->namelen`` bytes and
+ * ``rhs->name`` of length ``rhs->namelen`` bytes.  Returns negative
+ * integer if ``lhs->name`` is found to be less than ``rhs->name``; or
+ * returns positive integer if ``lhs->name`` is found to be greater
+ * than ``rhs->name``; or returns 0 otherwise.
+ */
+int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs);
+
+/**
+ * @function
+ *
+ * A helper function for dealing with NPN in client side or ALPN in
+ * server side.  The |in| contains peer's protocol list in preferable
+ * order.  The format of |in| is length-prefixed and not
+ * null-terminated.  For example, ``HTTP-draft-04/2.0`` and
+ * ``http/1.1`` stored in |in| like this::
+ *
+ *     in[0] = 17
+ *     in[1..17] = "HTTP-draft-04/2.0"
+ *     in[18] = 8
+ *     in[19..26] = "http/1.1"
+ *     inlen = 27
+ *
+ * The selection algorithm is as follows:
+ *
+ * 1. If peer's list contains HTTP/2 protocol the library supports,
+ *    it is selected and returns 1. The following step is not taken.
+ *
+ * 2. If peer's list contains ``http/1.1``, this function selects
+ *    ``http/1.1`` and returns 0.  The following step is not taken.
+ *
+ * 3. This function selects nothing and returns -1 (So called
+ *    non-overlap case).  In this case, |out| and |outlen| are left
+ *    untouched.
+ *
+ * Selecting ``HTTP-draft-04/2.0`` means that ``HTTP-draft-04/2.0`` is
+ * written into |*out| and its length (which is 17) is assigned to
+ * |*outlen|.
+ *
+ * For ALPN, refer to
+ * https://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-05
+ *
+ * See http://technotes.googlecode.com/git/nextprotoneg.html for more
+ * details about NPN.
+ *
+ * For NPN, to use this method you should do something like::
+ *
+ *     static int select_next_proto_cb(SSL* ssl,
+ *                                     unsigned char **out,
+ *                                     unsigned char *outlen,
+ *                                     const unsigned char *in,
+ *                                     unsigned int inlen,
+ *                                     void *arg)
+ *     {
+ *         int rv;
+ *         rv = nghttp2_select_next_protocol(out, outlen, in, inlen);
+ *         if(rv == 1) {
+ *             ((MyType*)arg)->http2_selected = 1;
+ *         }
+ *         return SSL_TLSEXT_ERR_OK;
+ *     }
+ *     ...
+ *     SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, my_obj);
+ *
+ */
+int nghttp2_select_next_protocol(unsigned char **out, unsigned char *outlen,
+                                 const unsigned char *in, unsigned int inlen);
+
+/**
+ * @function
+ *
+ * Returns a pointer to a nghttp2_info struct with version information
+ * about the run-time library in use.  The |least_version| argument
+ * can be set to a 24 bit numerical value for the least accepted
+ * version number and if the condition is not met, this function will
+ * return a ``NULL``.  Pass in 0 to skip the version checking.
+ */
+nghttp2_info *nghttp2_version(int least_version);
+
+/**
+ * @function
+ *
+ * Returns nonzero if the :type:`nghttp2_error` library error code
+ * |lib_error| is fatal.
+ */
+int nghttp2_is_fatal(int lib_error);
+
+/**
+ * @function
+ *
+ * Returns nonzero if HTTP header field name |name| of length |len| is
+ * valid according to http://tools.ietf.org/html/rfc7230#section-3.2
+ *
+ * Because this is a header field name in HTTP2, the upper cased alphabet
+ * is treated as error.
+ */
+int nghttp2_check_header_name(const uint8_t *name, size_t len);
+
+/**
+ * @function
+ *
+ * Returns nonzero if HTTP header field value |value| of length |len|
+ * is valid according to
+ * http://tools.ietf.org/html/rfc7230#section-3.2
+ */
+int nghttp2_check_header_value(const uint8_t *value, size_t len);
+
+/* HPACK API */
+
+struct nghttp2_hd_deflater;
+
+/**
+ * @struct
+ *
+ * HPACK deflater object.
+ */
+typedef struct nghttp2_hd_deflater nghttp2_hd_deflater;
+
+/**
+ * @function
+ *
+ * Initializes |*deflater_ptr| for deflating name/values pairs.
+ *
+ * The |deflate_hd_table_bufsize_max| is the upper bound of header
+ * table size the deflater will use.
+ *
+ * If this function fails, |*deflater_ptr| is left untouched.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
+                           size_t deflate_hd_table_bufsize_max);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_hd_deflate_new()`, but with additional custom memory
+ * allocator specified in the |mem|.
+ *
+ * The |mem| can be ``NULL`` and the call is equivalent to
+ * `nghttp2_hd_deflate_new()`.
+ *
+ * This function does not take ownership |mem|.  The application is
+ * responsible for freeing |mem|.
+ *
+ * The library code does not refer to |mem| pointer after this
+ * function returns, so the application can safely free it.
+ */
+int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr,
+                            size_t deflate_hd_table_bufsize_max,
+                            nghttp2_mem *mem);
+
+/**
+ * @function
+ *
+ * Deallocates any resources allocated for |deflater|.
+ */
+void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater);
+
+/**
+ * @function
+ *
+ * Changes header table size of the |deflater| to
+ * |settings_hd_table_bufsize_max| bytes.  This may trigger eviction
+ * in the dynamic table.
+ *
+ * The |settings_hd_table_bufsize_max| should be the value received in
+ * SETTINGS_HEADER_TABLE_SIZE.
+ *
+ * The deflater never uses more memory than
+ * ``deflate_hd_table_bufsize_max`` bytes specified in
+ * `nghttp2_hd_deflate_new()`.  Therefore, if
+ * |settings_hd_table_bufsize_max| > ``deflate_hd_table_bufsize_max``,
+ * resulting maximum table size becomes
+ * ``deflate_hd_table_bufsize_max``.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
+                                         size_t settings_hd_table_bufsize_max);
+
+/**
+ * @function
+ *
+ * Deflates the |nva|, which has the |nvlen| name/value pairs, into
+ * the |buf| of length |buflen|.
+ *
+ * If |buf| is not large enough to store the deflated header block,
+ * this function fails with :enum:`NGHTTP2_ERR_INSUFF_BUFSIZE`.  The
+ * caller should use `nghttp2_hd_deflate_bound()` to know the upper
+ * bound of buffer size required to deflate given header name/value
+ * pairs.
+ *
+ * Once this function fails, subsequent call of this function always
+ * returns :enum:`NGHTTP2_ERR_HEADER_COMP`.
+ *
+ * After this function returns, it is safe to delete the |nva|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_HEADER_COMP`
+ *     Deflation process has failed.
+ * :enum:`NGHTTP2_ERR_INSUFF_BUFSIZE`
+ *     The provided |buflen| size is too small to hold the output.
+ */
+ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf,
+                              size_t buflen, const nghttp2_nv *nva,
+                              size_t nvlen);
+
+/**
+ * @function
+ *
+ * Returns an upper bound on the compressed size after deflation of
+ * |nva| of length |nvlen|.
+ */
+size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
+                                const nghttp2_nv *nva, size_t nvlen);
+
+struct nghttp2_hd_inflater;
+
+/**
+ * @struct
+ *
+ * HPACK inflater object.
+ */
+typedef struct nghttp2_hd_inflater nghttp2_hd_inflater;
+
+/**
+ * @function
+ *
+ * Initializes |*inflater_ptr| for inflating name/values pairs.
+ *
+ * If this function fails, |*inflater_ptr| is left untouched.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr);
+
+/**
+ * @function
+ *
+ * Like `nghttp2_hd_inflate_new()`, but with additional custom memory
+ * allocator specified in the |mem|.
+ *
+ * The |mem| can be ``NULL`` and the call is equivalent to
+ * `nghttp2_hd_inflate_new()`.
+ *
+ * This function does not take ownership |mem|.  The application is
+ * responsible for freeing |mem|.
+ *
+ * The library code does not refer to |mem| pointer after this
+ * function returns, so the application can safely free it.
+ */
+int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr,
+                            nghttp2_mem *mem);
+
+/**
+ * @function
+ *
+ * Deallocates any resources allocated for |inflater|.
+ */
+void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater);
+
+/**
+ * @function
+ *
+ * Changes header table size in the |inflater|.  This may trigger
+ * eviction in the dynamic table.
+ *
+ * The |settings_hd_table_bufsize_max| should be the value transmitted
+ * in SETTINGS_HEADER_TABLE_SIZE.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
+                                         size_t settings_hd_table_bufsize_max);
+
+/**
+ * @enum
+ *
+ * The flags for header inflation.
+ */
+typedef enum {
+  /**
+   * No flag set.
+   */
+  NGHTTP2_HD_INFLATE_NONE = 0,
+  /**
+   * Indicates all headers were inflated.
+   */
+  NGHTTP2_HD_INFLATE_FINAL = 0x01,
+  /**
+   * Indicates a header was emitted.
+   */
+  NGHTTP2_HD_INFLATE_EMIT = 0x02
+} nghttp2_hd_inflate_flag;
+
+/**
+ * @function
+ *
+ * Inflates name/value block stored in |in| with length |inlen|.  This
+ * function performs decompression.  For each successful emission of
+ * header name/value pair, :enum:`NGHTTP2_HD_INFLATE_EMIT` is set in
+ * |*inflate_flags| and name/value pair is assigned to the |nv_out|
+ * and the function returns.  The caller must not free the members of
+ * |nv_out|.
+ *
+ * The |nv_out| may include pointers to the memory region in the |in|.
+ * The caller must retain the |in| while the |nv_out| is used.
+ *
+ * The application should call this function repeatedly until the
+ * ``(*inflate_flags) & NGHTTP2_HD_INFLATE_FINAL`` is nonzero and
+ * return value is non-negative.  This means the all input values are
+ * processed successfully.  Then the application must call
+ * `nghttp2_hd_inflate_end_headers()` to prepare for the next header
+ * block input.
+ *
+ * The caller can feed complete compressed header block.  It also can
+ * feed it in several chunks.  The caller must set |in_final| to
+ * nonzero if the given input is the last block of the compressed
+ * header.
+ *
+ * This function returns the number of bytes processed if it succeeds,
+ * or one of the following negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ * :enum:`NGHTTP2_ERR_HEADER_COMP`
+ *     Inflation process has failed.
+ * :enum:`NGHTTP2_ERR_BUFFER_ERROR`
+ *     The heder field name or value is too large.
+ *
+ * Example follows::
+ *
+ *     int inflate_header_block(nghttp2_hd_inflater *hd_inflater,
+ *                              uint8_t *in, size_t inlen, int final)
+ *     {
+ *         ssize_t rv;
+ *
+ *         for(;;) {
+ *             nghttp2_nv nv;
+ *             int inflate_flags = 0;
+ *
+ *             rv = nghttp2_hd_inflate_hd(hd_inflater, &nv, &inflate_flags,
+ *                                        in, inlen, final);
+ *
+ *             if(rv < 0) {
+ *                 fprintf(stderr, "inflate failed with error code %zd", rv);
+ *                 return -1;
+ *             }
+ *
+ *             in += rv;
+ *             inlen -= rv;
+ *
+ *             if(inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+ *                 fwrite(nv.name, nv.namelen, 1, stderr);
+ *                 fprintf(stderr, ": ");
+ *                 fwrite(nv.value, nv.valuelen, 1, stderr);
+ *                 fprintf(stderr, "\n");
+ *             }
+ *             if(inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+ *                 nghttp2_hd_inflate_end_headers(hd_inflater);
+ *                 break;
+ *             }
+ *             if((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 &&
+ *                inlen == 0) {
+ *                break;
+ *             }
+ *         }
+ *
+ *         return 0;
+ *     }
+ *
+ */
+ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out,
+                              int *inflate_flags, uint8_t *in, size_t inlen,
+                              int in_final);
+
+/**
+ * @function
+ *
+ * Signals the end of decompression for one header block.
+ *
+ * This function returns 0 if it succeeds. Currently this function
+ * always succeeds.
+ */
+int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NGHTTP2_H */
diff --git a/lib/includes/nghttp2/nghttp2ver.h.in b/lib/includes/nghttp2/nghttp2ver.h.in
new file mode 100644 (file)
index 0000000..7717a64
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2VER_H
+#define NGHTTP2VER_H
+
+/**
+ * @macro
+ * Version number of the nghttp2 library release
+ */
+#define NGHTTP2_VERSION "@PACKAGE_VERSION@"
+
+/**
+ * @macro
+ * Numerical representation of the version number of the nghttp2 library
+ * release. This is a 24 bit number with 8 bits for major number, 8 bits
+ * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
+ */
+#define NGHTTP2_VERSION_NUM @PACKAGE_VERSION_NUM@
+
+#endif /* NGHTTP2VER_H */
diff --git a/lib/libnghttp2.pc.in b/lib/libnghttp2.pc.in
new file mode 100644 (file)
index 0000000..e639e55
--- /dev/null
@@ -0,0 +1,33 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=/usr/lib
+includedir=/usr/include
+
+Name: libnghttp2
+Description: HTTP/2 C library
+URL: https://github.com/tatsuhiro-t/nghttp2
+Version: @VERSION@
+Libs: -L${libdir} -lnghttp2
+Cflags: -I${includedir}
diff --git a/lib/nghttp2_buf.c b/lib/nghttp2_buf.c
new file mode 100644 (file)
index 0000000..a9fd3f8
--- /dev/null
@@ -0,0 +1,484 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_buf.h"
+
+#include <stdio.h>
+
+#include "nghttp2_helper.h"
+
+void nghttp2_buf_init(nghttp2_buf *buf) {
+  buf->begin = NULL;
+  buf->end = NULL;
+  buf->pos = NULL;
+  buf->last = NULL;
+  buf->mark = NULL;
+}
+
+int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem) {
+  nghttp2_buf_init(buf);
+  return nghttp2_buf_reserve(buf, initial, mem);
+}
+
+void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem) {
+  if (buf == NULL) {
+    return;
+  }
+
+  nghttp2_mem_free(mem, buf->begin);
+  buf->begin = NULL;
+}
+
+int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem) {
+  uint8_t *ptr;
+  size_t cap;
+
+  cap = nghttp2_buf_cap(buf);
+
+  if (cap >= new_cap) {
+    return 0;
+  }
+
+  new_cap = nghttp2_max(new_cap, cap * 2);
+
+  ptr = nghttp2_mem_realloc(mem, buf->begin, new_cap);
+  if (ptr == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  buf->pos = ptr + (buf->pos - buf->begin);
+  buf->last = ptr + (buf->last - buf->begin);
+  buf->mark = ptr + (buf->mark - buf->begin);
+  buf->begin = ptr;
+  buf->end = ptr + new_cap;
+
+  return 0;
+}
+
+void nghttp2_buf_reset(nghttp2_buf *buf) {
+  buf->pos = buf->last = buf->mark = buf->begin;
+}
+
+void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len) {
+  buf->begin = buf->pos = buf->last = buf->mark = begin;
+  buf->end = begin + len;
+}
+
+static int buf_chain_new(nghttp2_buf_chain **chain, size_t chunk_length,
+                         nghttp2_mem *mem) {
+  int rv;
+
+  *chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain));
+  if (*chain == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  (*chain)->next = NULL;
+
+  rv = nghttp2_buf_init2(&(*chain)->buf, chunk_length, mem);
+  if (rv != 0) {
+    nghttp2_mem_free(mem, *chain);
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  return 0;
+}
+
+static void buf_chain_del(nghttp2_buf_chain *chain, nghttp2_mem *mem) {
+  nghttp2_buf_free(&chain->buf, mem);
+  nghttp2_mem_free(mem, chain);
+}
+
+int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk,
+                      nghttp2_mem *mem) {
+  return nghttp2_bufs_init2(bufs, chunk_length, max_chunk, 0, mem);
+}
+
+int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length,
+                       size_t max_chunk, size_t offset, nghttp2_mem *mem) {
+  return nghttp2_bufs_init3(bufs, chunk_length, max_chunk, max_chunk, offset,
+                            mem);
+}
+
+int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length,
+                       size_t max_chunk, size_t chunk_keep, size_t offset,
+                       nghttp2_mem *mem) {
+  int rv;
+  nghttp2_buf_chain *chain;
+
+  if (chunk_keep == 0 || max_chunk < chunk_keep || chunk_length < offset) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  rv = buf_chain_new(&chain, chunk_length, mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  bufs->mem = mem;
+  bufs->offset = offset;
+
+  bufs->head = chain;
+  bufs->cur = bufs->head;
+
+  nghttp2_buf_shift_right(&bufs->cur->buf, offset);
+
+  bufs->chunk_length = chunk_length;
+  bufs->chunk_used = 1;
+  bufs->max_chunk = max_chunk;
+  bufs->chunk_keep = chunk_keep;
+
+  return 0;
+}
+
+int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length) {
+  int rv;
+  nghttp2_buf_chain *chain;
+
+  if (chunk_length < bufs->offset) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  rv = buf_chain_new(&chain, chunk_length, bufs->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nghttp2_bufs_free(bufs);
+
+  bufs->head = chain;
+  bufs->cur = bufs->head;
+
+  nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset);
+
+  bufs->chunk_length = chunk_length;
+  bufs->chunk_used = 1;
+
+  return 0;
+}
+
+void nghttp2_bufs_free(nghttp2_bufs *bufs) {
+  nghttp2_buf_chain *chain, *next_chain;
+
+  if (bufs == NULL) {
+    return;
+  }
+
+  for (chain = bufs->head; chain;) {
+    next_chain = chain->next;
+
+    buf_chain_del(chain, bufs->mem);
+
+    chain = next_chain;
+  }
+
+  bufs->head = NULL;
+}
+
+int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len,
+                           nghttp2_mem *mem) {
+  nghttp2_buf_chain *chain;
+
+  chain = nghttp2_mem_malloc(mem, sizeof(nghttp2_buf_chain));
+  if (chain == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  chain->next = NULL;
+
+  nghttp2_buf_wrap_init(&chain->buf, begin, len);
+
+  bufs->mem = mem;
+  bufs->offset = 0;
+
+  bufs->head = chain;
+  bufs->cur = bufs->head;
+
+  bufs->chunk_length = len;
+  bufs->chunk_used = 1;
+  bufs->max_chunk = 1;
+  bufs->chunk_keep = 1;
+
+  return 0;
+}
+
+void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs) {
+  if (bufs == NULL) {
+    return;
+  }
+
+  nghttp2_mem_free(bufs->mem, bufs->head);
+  bufs->head = NULL;
+}
+
+void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs) {
+  nghttp2_buf_chain *ci;
+
+  for (ci = bufs->cur; ci; ci = ci->next) {
+    if (nghttp2_buf_len(&ci->buf) == 0) {
+      return;
+    } else {
+      bufs->cur = ci;
+    }
+  }
+}
+
+ssize_t nghttp2_bufs_len(nghttp2_bufs *bufs) {
+  nghttp2_buf_chain *ci;
+  ssize_t len;
+
+  len = 0;
+  for (ci = bufs->head; ci; ci = ci->next) {
+    len += nghttp2_buf_len(&ci->buf);
+  }
+
+  return len;
+}
+
+static ssize_t bufs_avail(nghttp2_bufs *bufs) {
+  return (ssize_t)(nghttp2_buf_avail(&bufs->cur->buf) +
+                   (bufs->chunk_length - bufs->offset) *
+                       (bufs->max_chunk - bufs->chunk_used));
+}
+
+static int bufs_alloc_chain(nghttp2_bufs *bufs) {
+  int rv;
+  nghttp2_buf_chain *chain;
+
+  if (bufs->cur->next) {
+    bufs->cur = bufs->cur->next;
+
+    return 0;
+  }
+
+  if (bufs->max_chunk == bufs->chunk_used) {
+    return NGHTTP2_ERR_BUFFER_ERROR;
+  }
+
+  rv = buf_chain_new(&chain, bufs->chunk_length, bufs->mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  DEBUGF(fprintf(stderr,
+                 "new buffer %zu bytes allocated for bufs %p, used %zu\n",
+                 bufs->chunk_length, bufs, bufs->chunk_used));
+
+  ++bufs->chunk_used;
+
+  bufs->cur->next = chain;
+  bufs->cur = chain;
+
+  nghttp2_buf_shift_right(&bufs->cur->buf, bufs->offset);
+
+  return 0;
+}
+
+int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len) {
+  int rv;
+  size_t nwrite;
+  nghttp2_buf *buf;
+  const uint8_t *p;
+
+  if (bufs_avail(bufs) < (ssize_t)len) {
+    return NGHTTP2_ERR_BUFFER_ERROR;
+  }
+
+  p = data;
+
+  while (len) {
+    buf = &bufs->cur->buf;
+
+    nwrite = nghttp2_min((size_t)nghttp2_buf_avail(buf), len);
+    if (nwrite == 0) {
+      rv = bufs_alloc_chain(bufs);
+      if (rv != 0) {
+        return rv;
+      }
+      continue;
+    }
+
+    buf->last = nghttp2_cpymem(buf->last, p, nwrite);
+    p += len;
+    len -= nwrite;
+  }
+
+  return 0;
+}
+
+static int bufs_ensure_addb(nghttp2_bufs *bufs) {
+  int rv;
+  nghttp2_buf *buf;
+
+  buf = &bufs->cur->buf;
+
+  if (nghttp2_buf_avail(buf) > 0) {
+    return 0;
+  }
+
+  rv = bufs_alloc_chain(bufs);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b) {
+  int rv;
+
+  rv = bufs_ensure_addb(bufs);
+  if (rv != 0) {
+    return rv;
+  }
+
+  *bufs->cur->buf.last++ = b;
+
+  return 0;
+}
+
+int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b) {
+  int rv;
+
+  rv = bufs_ensure_addb(bufs);
+  if (rv != 0) {
+    return rv;
+  }
+
+  *bufs->cur->buf.last = b;
+
+  return 0;
+}
+
+int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b) {
+  int rv;
+
+  rv = bufs_ensure_addb(bufs);
+  if (rv != 0) {
+    return rv;
+  }
+
+  *bufs->cur->buf.last++ |= b;
+
+  return 0;
+}
+
+int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b) {
+  int rv;
+
+  rv = bufs_ensure_addb(bufs);
+  if (rv != 0) {
+    return rv;
+  }
+
+  *bufs->cur->buf.last |= b;
+
+  return 0;
+}
+
+ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out) {
+  size_t len;
+  nghttp2_buf_chain *chain;
+  nghttp2_buf *buf;
+  uint8_t *res;
+  nghttp2_buf resbuf;
+
+  len = 0;
+
+  for (chain = bufs->head; chain; chain = chain->next) {
+    len += nghttp2_buf_len(&chain->buf);
+  }
+
+  if (!len) {
+    res = NULL;
+  } else {
+    res = nghttp2_mem_malloc(bufs->mem, len);
+
+    if (res == NULL) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+  }
+
+  nghttp2_buf_wrap_init(&resbuf, res, len);
+
+  for (chain = bufs->head; chain; chain = chain->next) {
+    buf = &chain->buf;
+
+    if (resbuf.last) {
+      resbuf.last = nghttp2_cpymem(resbuf.last, buf->pos, nghttp2_buf_len(buf));
+    }
+
+    nghttp2_buf_reset(buf);
+    nghttp2_buf_shift_right(&chain->buf, bufs->offset);
+  }
+
+  bufs->cur = bufs->head;
+
+  *out = res;
+
+  return (ssize_t)len;
+}
+
+void nghttp2_bufs_reset(nghttp2_bufs *bufs) {
+  nghttp2_buf_chain *chain, *ci;
+  size_t k;
+
+  k = bufs->chunk_keep;
+
+  for (ci = bufs->head; ci; ci = ci->next) {
+    nghttp2_buf_reset(&ci->buf);
+    nghttp2_buf_shift_right(&ci->buf, bufs->offset);
+
+    if (--k == 0) {
+      break;
+    }
+  }
+
+  if (ci) {
+    chain = ci->next;
+    ci->next = NULL;
+
+    for (ci = chain; ci;) {
+      chain = ci->next;
+
+      buf_chain_del(ci, bufs->mem);
+
+      ci = chain;
+    }
+
+    bufs->chunk_used = bufs->chunk_keep;
+  }
+
+  bufs->cur = bufs->head;
+}
+
+int nghttp2_bufs_advance(nghttp2_bufs *bufs) { return bufs_alloc_chain(bufs); }
+
+int nghttp2_bufs_next_present(nghttp2_bufs *bufs) {
+  nghttp2_buf_chain *chain;
+
+  chain = bufs->cur->next;
+
+  return chain && nghttp2_buf_len(&chain->buf);
+}
diff --git a/lib/nghttp2_buf.h b/lib/nghttp2_buf.h
new file mode 100644 (file)
index 0000000..c045518
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_BUF_H
+#define NGHTTP2_BUF_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+#include "nghttp2_int.h"
+#include "nghttp2_mem.h"
+
+typedef struct {
+  /* This points to the beginning of the buffer. The effective range
+     of buffer is [begin, end). */
+  uint8_t *begin;
+  /* This points to the memory one byte beyond the end of the
+     buffer. */
+  uint8_t *end;
+  /* The position indicator for effective start of the buffer. pos <=
+     last must be hold. */
+  uint8_t *pos;
+  /* The position indicator for effective one beyond of the end of the
+     buffer. last <= end must be hold. */
+  uint8_t *last;
+  /* Mark arbitrary position in buffer [begin, end) */
+  uint8_t *mark;
+} nghttp2_buf;
+
+#define nghttp2_buf_len(BUF) ((ssize_t)((BUF)->last - (BUF)->pos))
+#define nghttp2_buf_avail(BUF) ((ssize_t)((BUF)->end - (BUF)->last))
+#define nghttp2_buf_mark_avail(BUF) ((ssize_t)((BUF)->mark - (BUF)->last))
+#define nghttp2_buf_cap(BUF) ((ssize_t)((BUF)->end - (BUF)->begin))
+
+#define nghttp2_buf_pos_offset(BUF) ((ssize_t)((BUF)->pos - (BUF)->begin))
+#define nghttp2_buf_last_offset(BUF) ((ssize_t)((BUF)->last - (BUF)->begin))
+
+#define nghttp2_buf_shift_right(BUF, AMT)                                      \
+  do {                                                                         \
+    (BUF)->pos += AMT;                                                         \
+    (BUF)->last += AMT;                                                        \
+  } while (0)
+
+#define nghttp2_buf_shift_left(BUF, AMT)                                       \
+  do {                                                                         \
+    (BUF)->pos -= AMT;                                                         \
+    (BUF)->last -= AMT;                                                        \
+  } while (0)
+
+/*
+ * Initializes the |buf|. No memory is allocated in this function. Use
+ * nghttp2_buf_reserve() or nghttp2_buf_reserve2() to allocate memory.
+ */
+void nghttp2_buf_init(nghttp2_buf *buf);
+
+/*
+ * Initializes the |buf| and allocates at least |initial| bytes of
+ * memory.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_buf_init2(nghttp2_buf *buf, size_t initial, nghttp2_mem *mem);
+
+/*
+ * Frees buffer in |buf|.
+ */
+void nghttp2_buf_free(nghttp2_buf *buf, nghttp2_mem *mem);
+
+/*
+ * Extends buffer so that nghttp2_buf_cap() returns at least
+ * |new_cap|. If extensions took place, buffer pointers in |buf| will
+ * change.
+ *
+ * This function returns 0 if it succeeds, or one of the followings
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_buf_reserve(nghttp2_buf *buf, size_t new_cap, nghttp2_mem *mem);
+
+/*
+ * Resets pos, last, mark member of |buf| to buf->begin.
+ */
+void nghttp2_buf_reset(nghttp2_buf *buf);
+
+/*
+ * Initializes |buf| using supplied buffer |begin| of length
+ * |len|. Semantically, the application should not call *_reserve() or
+ * nghttp2_free() functions for |buf|.
+ */
+void nghttp2_buf_wrap_init(nghttp2_buf *buf, uint8_t *begin, size_t len);
+
+struct nghttp2_buf_chain;
+
+typedef struct nghttp2_buf_chain nghttp2_buf_chain;
+
+/* Chains 2 buffers */
+struct nghttp2_buf_chain {
+  /* Points to the subsequent buffer. NULL if there is no such
+     buffer. */
+  nghttp2_buf_chain *next;
+  nghttp2_buf buf;
+};
+
+typedef struct {
+  /* Points to the first buffer */
+  nghttp2_buf_chain *head;
+  /* Buffer pointer where write occurs. */
+  nghttp2_buf_chain *cur;
+  /* Memory allocator */
+  nghttp2_mem *mem;
+  /* The buffer capacity of each buf */
+  size_t chunk_length;
+  /* The maximum number of nghttp2_buf_chain */
+  size_t max_chunk;
+  /* The number of nghttp2_buf_chain allocated */
+  size_t chunk_used;
+  /* The number of nghttp2_buf_chain to keep on reset */
+  size_t chunk_keep;
+  /* pos offset from begin in each buffers. On initialization and
+     reset, buf->pos and buf->last are positioned at buf->begin +
+     offset. */
+  size_t offset;
+} nghttp2_bufs;
+
+/*
+ * This is the same as calling nghttp2_bufs_init2 with the given
+ * arguments and offset = 0.
+ */
+int nghttp2_bufs_init(nghttp2_bufs *bufs, size_t chunk_length, size_t max_chunk,
+                      nghttp2_mem *mem);
+
+/*
+ * This is the same as calling nghttp2_bufs_init3 with the given
+ * arguments and chunk_keep = max_chunk.
+ */
+int nghttp2_bufs_init2(nghttp2_bufs *bufs, size_t chunk_length,
+                       size_t max_chunk, size_t offset, nghttp2_mem *mem);
+
+/*
+ * Initializes |bufs|. Each buffer size is given in the
+ * |chunk_length|.  The maximum number of buffers is given in the
+ * |max_chunk|.  On reset, first |chunk_keep| buffers are kept and
+ * remaining buffers are deleted.  Each buffer will have bufs->pos and
+ * bufs->last shifted to left by |offset| bytes on creation and reset.
+ *
+ * This function allocates first buffer.  bufs->head and bufs->cur
+ * will point to the first buffer after this call.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     chunk_keep is 0; or max_chunk < chunk_keep; or offset is too
+ *     long.
+ */
+int nghttp2_bufs_init3(nghttp2_bufs *bufs, size_t chunk_length,
+                       size_t max_chunk, size_t chunk_keep, size_t offset,
+                       nghttp2_mem *mem);
+
+/*
+ * Frees any related resources to the |bufs|.
+ */
+void nghttp2_bufs_free(nghttp2_bufs *bufs);
+
+/*
+ * Initializes |bufs| using supplied buffer |begin| of length |len|.
+ * The first buffer bufs->head uses buffer |begin|.  The buffer size
+ * is fixed and no allocate extra chunk buffer is allocated.  In other
+ * words, max_chunk = chunk_keep = 1.  To free the resource allocated
+ * for |bufs|, use nghttp2_bufs_wrap_free().
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_bufs_wrap_init(nghttp2_bufs *bufs, uint8_t *begin, size_t len,
+                           nghttp2_mem *mem);
+
+/*
+ * Frees any related resource to the |bufs|.  This function does not
+ * free supplied buffer provided in nghttp2_bufs_wrap_init().
+ */
+void nghttp2_bufs_wrap_free(nghttp2_bufs *bufs);
+
+/*
+ * Reallocates internal buffer using |chunk_length|.  The max_chunk,
+ * chunk_keep and offset do not change.  After successful allocation
+ * of new buffer, previous buffers are deallocated without copying
+ * anything into new buffers.  chunk_used is reset to 1.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     chunk_length < offset
+ */
+int nghttp2_bufs_realloc(nghttp2_bufs *bufs, size_t chunk_length);
+
+/*
+ * Appends the |data| of length |len| to the |bufs|. The write starts
+ * at bufs->cur->buf.last. A new buffers will be allocated to store
+ * all data.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+int nghttp2_bufs_add(nghttp2_bufs *bufs, const void *data, size_t len);
+
+/*
+ * Appends a single byte |b| to the |bufs|. The write starts at
+ * bufs->cur->buf.last. A new buffers will be allocated to store all
+ * data.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+int nghttp2_bufs_addb(nghttp2_bufs *bufs, uint8_t b);
+
+/*
+ * Behaves like nghttp2_bufs_addb(), but this does not update
+ * buf->last pointer.
+ */
+int nghttp2_bufs_addb_hold(nghttp2_bufs *bufs, uint8_t b);
+
+#define nghttp2_bufs_fast_addb(BUFS, B)                                        \
+  do {                                                                         \
+    *(BUFS)->cur->buf.last++ = B;                                              \
+  } while (0)
+
+#define nghttp2_bufs_fast_addb_hold(BUFS, B)                                   \
+  do {                                                                         \
+    *(BUFS)->cur->buf.last = B;                                                \
+  } while (0)
+
+/*
+ * Performs bitwise-OR of |b| at bufs->cur->buf.last. A new buffers
+ * will be allocated if necessary.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+int nghttp2_bufs_orb(nghttp2_bufs *bufs, uint8_t b);
+
+/*
+ * Behaves like nghttp2_bufs_orb(), but does not update buf->last
+ * pointer.
+ */
+int nghttp2_bufs_orb_hold(nghttp2_bufs *bufs, uint8_t b);
+
+#define nghttp2_bufs_fast_orb(BUFS, B)                                         \
+  do {                                                                         \
+    *(BUFS)->cur->buf.last++ |= B;                                             \
+  } while (0)
+
+#define nghttp2_bufs_fast_orb_hold(BUFS, B)                                    \
+  do {                                                                         \
+    *(BUFS)->cur->buf.last |= B;                                               \
+  } while (0)
+
+/*
+ * Copies all data stored in |bufs| to the contagious buffer.  This
+ * function allocates the contagious memory to store all data in
+ * |bufs| and assigns it to |*out|.
+ *
+ * On successful return, nghttp2_bufs_len(bufs) returns 0, just like
+ * after calling nghttp2_bufs_reset().
+
+ * This function returns the length of copied data and assigns the
+ * pointer to copied data to |*out| if it succeeds, or one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+ssize_t nghttp2_bufs_remove(nghttp2_bufs *bufs, uint8_t **out);
+
+/*
+ * Resets |bufs| and makes the buffers empty.
+ */
+void nghttp2_bufs_reset(nghttp2_bufs *bufs);
+
+/*
+ * Moves bufs->cur to bufs->cur->next.  If resulting bufs->cur is
+ * NULL, this function allocates new buffers and bufs->cur points to
+ * it.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+int nghttp2_bufs_advance(nghttp2_bufs *bufs);
+
+/* Sets bufs->cur to bufs->head */
+#define nghttp2_bufs_rewind(BUFS)                                              \
+  do {                                                                         \
+    (BUFS)->cur = (BUFS)->head;                                                \
+  } while (0)
+
+/*
+ * Move bufs->cur, from the current position, using next member, to
+ * the last buf which has nghttp2_buf_len(buf) > 0 without seeing buf
+ * which satisfies nghttp2_buf_len(buf) == 0.  If
+ * nghttp2_buf_len(&bufs->cur->buf) == 0 or bufs->cur->next is NULL,
+ * bufs->cur is unchanged.
+ */
+void nghttp2_bufs_seek_last_present(nghttp2_bufs *bufs);
+
+/*
+ * Returns nonzero if bufs->cur->next is not emtpy.
+ */
+int nghttp2_bufs_next_present(nghttp2_bufs *bufs);
+
+#define nghttp2_bufs_cur_avail(BUFS) nghttp2_buf_avail(&(BUFS)->cur->buf)
+
+/*
+ * Returns the buffer length of |bufs|.
+ */
+ssize_t nghttp2_bufs_len(nghttp2_bufs *bufs);
+
+#endif /* NGHTTP2_BUF_H */
diff --git a/lib/nghttp2_callbacks.c b/lib/nghttp2_callbacks.c
new file mode 100644 (file)
index 0000000..f4bda4e
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_callbacks.h"
+
+#include <stdlib.h>
+
+int nghttp2_session_callbacks_new(nghttp2_session_callbacks **callbacks_ptr) {
+  *callbacks_ptr = calloc(1, sizeof(nghttp2_session_callbacks));
+
+  if (*callbacks_ptr == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  return 0;
+}
+
+void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks) {
+  free(callbacks);
+}
+
+void nghttp2_session_callbacks_set_send_callback(
+    nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback) {
+  cbs->send_callback = send_callback;
+}
+
+void nghttp2_session_callbacks_set_recv_callback(
+    nghttp2_session_callbacks *cbs, nghttp2_recv_callback recv_callback) {
+  cbs->recv_callback = recv_callback;
+}
+
+void nghttp2_session_callbacks_set_on_frame_recv_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_frame_recv_callback on_frame_recv_callback) {
+  cbs->on_frame_recv_callback = on_frame_recv_callback;
+}
+
+void nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback) {
+  cbs->on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+}
+
+void nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback) {
+  cbs->on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+}
+
+void nghttp2_session_callbacks_set_before_frame_send_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_before_frame_send_callback before_frame_send_callback) {
+  cbs->before_frame_send_callback = before_frame_send_callback;
+}
+
+void nghttp2_session_callbacks_set_on_frame_send_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_frame_send_callback on_frame_send_callback) {
+  cbs->on_frame_send_callback = on_frame_send_callback;
+}
+
+void nghttp2_session_callbacks_set_on_frame_not_send_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_frame_not_send_callback on_frame_not_send_callback) {
+  cbs->on_frame_not_send_callback = on_frame_not_send_callback;
+}
+
+void nghttp2_session_callbacks_set_on_stream_close_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_stream_close_callback on_stream_close_callback) {
+  cbs->on_stream_close_callback = on_stream_close_callback;
+}
+
+void nghttp2_session_callbacks_set_on_begin_headers_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_begin_headers_callback on_begin_headers_callback) {
+  cbs->on_begin_headers_callback = on_begin_headers_callback;
+}
+
+void nghttp2_session_callbacks_set_on_header_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_header_callback on_header_callback) {
+  cbs->on_header_callback = on_header_callback;
+}
+
+void nghttp2_session_callbacks_set_select_padding_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_select_padding_callback select_padding_callback) {
+  cbs->select_padding_callback = select_padding_callback;
+}
+
+void nghttp2_session_callbacks_set_data_source_read_length_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_data_source_read_length_callback data_source_read_length_callback) {
+  cbs->read_length_callback = data_source_read_length_callback;
+}
+
+void nghttp2_session_callbacks_set_on_begin_frame_callback(
+    nghttp2_session_callbacks *cbs,
+    nghttp2_on_begin_frame_callback on_begin_frame_callback) {
+  cbs->on_begin_frame_callback = on_begin_frame_callback;
+}
diff --git a/lib/nghttp2_callbacks.h b/lib/nghttp2_callbacks.h
new file mode 100644 (file)
index 0000000..df3ddba
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_CALLBACKS_H
+#define NGHTTP2_CALLBACKS_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+/*
+ * Callback functions.
+ */
+struct nghttp2_session_callbacks {
+  /**
+   * Callback function invoked when the session wants to send data to
+   * the remote peer.  This callback is not necessary if the
+   * application uses solely `nghttp2_session_mem_send()` to serialize
+   * data to transmit.
+   */
+  nghttp2_send_callback send_callback;
+  /**
+   * Callback function invoked when the session wants to receive data
+   * from the remote peer.  This callback is not necessary if the
+   * application uses solely `nghttp2_session_mem_recv()` to process
+   * received data.
+   */
+  nghttp2_recv_callback recv_callback;
+  /**
+   * Callback function invoked by `nghttp2_session_recv()` when a
+   * frame is received.
+   */
+  nghttp2_on_frame_recv_callback on_frame_recv_callback;
+  /**
+   * Callback function invoked by `nghttp2_session_recv()` when an
+   * invalid non-DATA frame is received.
+   */
+  nghttp2_on_invalid_frame_recv_callback on_invalid_frame_recv_callback;
+  /**
+   * Callback function invoked when a chunk of data in DATA frame is
+   * received.
+   */
+  nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback;
+  /**
+   * Callback function invoked before a non-DATA frame is sent.
+   */
+  nghttp2_before_frame_send_callback before_frame_send_callback;
+  /**
+   * Callback function invoked after a frame is sent.
+   */
+  nghttp2_on_frame_send_callback on_frame_send_callback;
+  /**
+   * The callback function invoked when a non-DATA frame is not sent
+   * because of an error.
+   */
+  nghttp2_on_frame_not_send_callback on_frame_not_send_callback;
+  /**
+   * Callback function invoked when the stream is closed.
+   */
+  nghttp2_on_stream_close_callback on_stream_close_callback;
+  /**
+   * Callback function invoked when the reception of header block in
+   * HEADERS or PUSH_PROMISE is started.
+   */
+  nghttp2_on_begin_headers_callback on_begin_headers_callback;
+  /**
+   * Callback function invoked when a header name/value pair is
+   * received.
+   */
+  nghttp2_on_header_callback on_header_callback;
+  /**
+   * Callback function invoked when the library asks application how
+   * many padding bytes are required for the transmission of the given
+   * frame.
+   */
+  nghttp2_select_padding_callback select_padding_callback;
+  /**
+   * The callback function used to determine the length allowed in
+   * `nghttp2_data_source_read_callback()`
+   */
+  nghttp2_data_source_read_length_callback read_length_callback;
+  /**
+   * Sets callback function invoked when a frame header is received.
+   */
+  nghttp2_on_begin_frame_callback on_begin_frame_callback;
+};
+
+#endif /* NGHTTP2_CALLBACKS_H */
diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c
new file mode 100644 (file)
index 0000000..d97194b
--- /dev/null
@@ -0,0 +1,883 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_frame.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include "nghttp2_helper.h"
+#include "nghttp2_net.h"
+#include "nghttp2_priority_spec.h"
+
+void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd) {
+  nghttp2_put_uint32be(&buf[0], (uint32_t)(hd->length << 8));
+  buf[3] = hd->type;
+  buf[4] = hd->flags;
+  nghttp2_put_uint32be(&buf[5], hd->stream_id);
+  /* ignore hd->reserved for now */
+}
+
+void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t *buf) {
+  hd->length = nghttp2_get_uint32(&buf[0]) >> 8;
+  hd->type = buf[3];
+  hd->flags = buf[4];
+  hd->stream_id = nghttp2_get_uint32(&buf[5]) & NGHTTP2_STREAM_ID_MASK;
+  hd->reserved = 0;
+}
+
+void nghttp2_frame_hd_init(nghttp2_frame_hd *hd, size_t length, uint8_t type,
+                           uint8_t flags, int32_t stream_id) {
+  hd->length = length;
+  hd->type = type;
+  hd->flags = flags;
+  hd->stream_id = stream_id;
+  hd->reserved = 0;
+}
+
+void nghttp2_frame_headers_init(nghttp2_headers *frame, uint8_t flags,
+                                int32_t stream_id, nghttp2_headers_category cat,
+                                const nghttp2_priority_spec *pri_spec,
+                                nghttp2_nv *nva, size_t nvlen) {
+  nghttp2_frame_hd_init(&frame->hd, 0, NGHTTP2_HEADERS, flags, stream_id);
+  frame->padlen = 0;
+  frame->nva = nva;
+  frame->nvlen = nvlen;
+  frame->cat = cat;
+
+  if (pri_spec) {
+    frame->pri_spec = *pri_spec;
+  } else {
+    nghttp2_priority_spec_default_init(&frame->pri_spec);
+  }
+}
+
+void nghttp2_frame_headers_free(nghttp2_headers *frame, nghttp2_mem *mem) {
+  nghttp2_nv_array_del(frame->nva, mem);
+}
+
+void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id,
+                                 const nghttp2_priority_spec *pri_spec) {
+  nghttp2_frame_hd_init(&frame->hd, NGHTTP2_PRIORITY_SPECLEN, NGHTTP2_PRIORITY,
+                        NGHTTP2_FLAG_NONE, stream_id);
+  frame->pri_spec = *pri_spec;
+}
+
+void nghttp2_frame_priority_free(nghttp2_priority *frame _U_) {}
+
+void nghttp2_frame_rst_stream_init(nghttp2_rst_stream *frame, int32_t stream_id,
+                                   uint32_t error_code) {
+  nghttp2_frame_hd_init(&frame->hd, 4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE,
+                        stream_id);
+  frame->error_code = error_code;
+}
+
+void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame _U_) {}
+
+void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags,
+                                 nghttp2_settings_entry *iv, size_t niv) {
+  nghttp2_frame_hd_init(&frame->hd, niv * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH,
+                        NGHTTP2_SETTINGS, flags, 0);
+  frame->niv = niv;
+  frame->iv = iv;
+}
+
+void nghttp2_frame_settings_free(nghttp2_settings *frame, nghttp2_mem *mem) {
+  nghttp2_mem_free(mem, frame->iv);
+}
+
+void nghttp2_frame_push_promise_init(nghttp2_push_promise *frame, uint8_t flags,
+                                     int32_t stream_id,
+                                     int32_t promised_stream_id,
+                                     nghttp2_nv *nva, size_t nvlen) {
+  nghttp2_frame_hd_init(&frame->hd, 0, NGHTTP2_PUSH_PROMISE, flags, stream_id);
+  frame->padlen = 0;
+  frame->nva = nva;
+  frame->nvlen = nvlen;
+  frame->promised_stream_id = promised_stream_id;
+  frame->reserved = 0;
+}
+
+void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame,
+                                     nghttp2_mem *mem) {
+  nghttp2_nv_array_del(frame->nva, mem);
+}
+
+void nghttp2_frame_ping_init(nghttp2_ping *frame, uint8_t flags,
+                             const uint8_t *opaque_data) {
+  nghttp2_frame_hd_init(&frame->hd, 8, NGHTTP2_PING, flags, 0);
+  if (opaque_data) {
+    memcpy(frame->opaque_data, opaque_data, sizeof(frame->opaque_data));
+  } else {
+    memset(frame->opaque_data, 0, sizeof(frame->opaque_data));
+  }
+}
+
+void nghttp2_frame_ping_free(nghttp2_ping *frame _U_) {}
+
+void nghttp2_frame_goaway_init(nghttp2_goaway *frame, int32_t last_stream_id,
+                               uint32_t error_code, uint8_t *opaque_data,
+                               size_t opaque_data_len) {
+  nghttp2_frame_hd_init(&frame->hd, 8 + opaque_data_len, NGHTTP2_GOAWAY,
+                        NGHTTP2_FLAG_NONE, 0);
+  frame->last_stream_id = last_stream_id;
+  frame->error_code = error_code;
+  frame->opaque_data = opaque_data;
+  frame->opaque_data_len = opaque_data_len;
+  frame->reserved = 0;
+}
+
+void nghttp2_frame_goaway_free(nghttp2_goaway *frame, nghttp2_mem *mem) {
+  nghttp2_mem_free(mem, frame->opaque_data);
+}
+
+void nghttp2_frame_window_update_init(nghttp2_window_update *frame,
+                                      uint8_t flags, int32_t stream_id,
+                                      int32_t window_size_increment) {
+  nghttp2_frame_hd_init(&frame->hd, 4, NGHTTP2_WINDOW_UPDATE, flags, stream_id);
+  frame->window_size_increment = window_size_increment;
+  frame->reserved = 0;
+}
+
+void nghttp2_frame_window_update_free(nghttp2_window_update *frame _U_) {}
+
+size_t nghttp2_frame_trail_padlen(nghttp2_frame *frame, size_t padlen) {
+  return padlen - ((frame->hd.flags & NGHTTP2_FLAG_PADDED) > 0);
+}
+
+void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags,
+                             int32_t stream_id) {
+  /* At this moment, the length of DATA frame is unknown */
+  nghttp2_frame_hd_init(&frame->hd, 0, NGHTTP2_DATA, flags, stream_id);
+  frame->padlen = 0;
+}
+
+void nghttp2_frame_data_free(nghttp2_data *frame _U_) {}
+
+size_t nghttp2_frame_priority_len(uint8_t flags) {
+  if (flags & NGHTTP2_FLAG_PRIORITY) {
+    return NGHTTP2_PRIORITY_SPECLEN;
+  }
+
+  return 0;
+}
+
+size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame) {
+  return nghttp2_frame_priority_len(frame->hd.flags);
+}
+
+/*
+ * Call this function after payload was serialized, but not before
+ * changing buf->pos and serializing frame header.
+ *
+ * This function assumes bufs->cur points to the last buf chain of the
+ * frame(s).
+ *
+ * This function serializes frame header for HEADERS/PUSH_PROMISE and
+ * handles their successive CONTINUATION frames.
+ *
+ * We don't process any padding here.
+ */
+static int frame_pack_headers_shared(nghttp2_bufs *bufs,
+                                     nghttp2_frame_hd *frame_hd) {
+  nghttp2_buf *buf;
+  nghttp2_buf_chain *ci, *ce;
+  nghttp2_frame_hd hd;
+
+  buf = &bufs->head->buf;
+
+  hd = *frame_hd;
+  hd.length = nghttp2_buf_len(buf);
+
+  DEBUGF(fprintf(stderr, "send: HEADERS/PUSH_PROMISE, payloadlen=%zu\n",
+                 hd.length));
+
+  /* We have multiple frame buffers, which means one or more
+     CONTINUATION frame is involved. Remove END_HEADERS flag from the
+     first frame. */
+  if (bufs->head != bufs->cur) {
+    hd.flags &= ~NGHTTP2_FLAG_END_HEADERS;
+  }
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+  nghttp2_frame_pack_frame_hd(buf->pos, &hd);
+
+  if (bufs->head != bufs->cur) {
+    /* 2nd and later frames are CONTINUATION frames. */
+    hd.type = NGHTTP2_CONTINUATION;
+    /* We don't have no flags except for last CONTINUATION */
+    hd.flags = NGHTTP2_FLAG_NONE;
+
+    ce = bufs->cur;
+
+    for (ci = bufs->head->next; ci != ce; ci = ci->next) {
+      buf = &ci->buf;
+
+      hd.length = nghttp2_buf_len(buf);
+
+      DEBUGF(fprintf(stderr, "send: int CONTINUATION, payloadlen=%zu\n",
+                     hd.length));
+
+      buf->pos -= NGHTTP2_FRAME_HDLEN;
+      nghttp2_frame_pack_frame_hd(buf->pos, &hd);
+    }
+
+    buf = &ci->buf;
+    hd.length = nghttp2_buf_len(buf);
+    /* Set END_HEADERS flag for last CONTINUATION */
+    hd.flags = NGHTTP2_FLAG_END_HEADERS;
+
+    DEBUGF(fprintf(stderr, "send: last CONTINUATION, payloadlen=%zu\n",
+                   hd.length));
+
+    buf->pos -= NGHTTP2_FRAME_HDLEN;
+    nghttp2_frame_pack_frame_hd(buf->pos, &hd);
+  }
+
+  return 0;
+}
+
+int nghttp2_frame_pack_headers(nghttp2_bufs *bufs, nghttp2_headers *frame,
+                               nghttp2_hd_deflater *deflater) {
+  size_t nv_offset;
+  int rv;
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  nv_offset = nghttp2_frame_headers_payload_nv_offset(frame);
+
+  buf = &bufs->cur->buf;
+
+  buf->pos += nv_offset;
+  buf->last = buf->pos;
+
+  /* This call will adjust buf->last to the correct position */
+  rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, frame->nva, frame->nvlen);
+
+  if (rv == NGHTTP2_ERR_BUFFER_ERROR) {
+    rv = NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  buf->pos -= nv_offset;
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
+    nghttp2_frame_pack_priority_spec(buf->pos, &frame->pri_spec);
+  }
+
+  frame->padlen = 0;
+  frame->hd.length = nghttp2_bufs_len(bufs);
+
+  return frame_pack_headers_shared(bufs, &frame->hd);
+}
+
+void nghttp2_frame_pack_priority_spec(uint8_t *buf,
+                                      const nghttp2_priority_spec *pri_spec) {
+  nghttp2_put_uint32be(buf, pri_spec->stream_id);
+  if (pri_spec->exclusive) {
+    buf[0] |= 0x80;
+  }
+  buf[4] = pri_spec->weight - 1;
+}
+
+void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec,
+                                        uint8_t flags _U_,
+                                        const uint8_t *payload,
+                                        size_t payloadlen _U_) {
+  int32_t dep_stream_id;
+  uint8_t exclusive;
+  int32_t weight;
+
+  dep_stream_id = nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK;
+  exclusive = (payload[0] & 0x80) > 0;
+  weight = payload[4] + 1;
+
+  nghttp2_priority_spec_init(pri_spec, dep_stream_id, weight, exclusive);
+}
+
+int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame,
+                                         const uint8_t *payload,
+                                         size_t payloadlen) {
+  if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
+    nghttp2_frame_unpack_priority_spec(&frame->pri_spec, frame->hd.flags,
+                                       payload, payloadlen);
+  } else {
+    nghttp2_priority_spec_default_init(&frame->pri_spec);
+  }
+
+  frame->nva = NULL;
+  frame->nvlen = 0;
+
+  return 0;
+}
+
+int nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame) {
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->head->buf;
+
+  assert(nghttp2_buf_avail(buf) >= NGHTTP2_PRIORITY_SPECLEN);
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  nghttp2_frame_pack_priority_spec(buf->last, &frame->pri_spec);
+
+  buf->last += NGHTTP2_PRIORITY_SPECLEN;
+
+  return 0;
+}
+
+void nghttp2_frame_unpack_priority_payload(nghttp2_priority *frame,
+                                           const uint8_t *payload,
+                                           size_t payloadlen) {
+  nghttp2_frame_unpack_priority_spec(&frame->pri_spec, frame->hd.flags, payload,
+                                     payloadlen);
+}
+
+int nghttp2_frame_pack_rst_stream(nghttp2_bufs *bufs,
+                                  nghttp2_rst_stream *frame) {
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->head->buf;
+
+  assert(nghttp2_buf_avail(buf) >= 4);
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  nghttp2_put_uint32be(buf->last, frame->error_code);
+  buf->last += 4;
+
+  return 0;
+}
+
+void nghttp2_frame_unpack_rst_stream_payload(nghttp2_rst_stream *frame,
+                                             const uint8_t *payload,
+                                             size_t payloadlen _U_) {
+  frame->error_code = nghttp2_get_uint32(payload);
+}
+
+int nghttp2_frame_pack_settings(nghttp2_bufs *bufs, nghttp2_settings *frame) {
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->head->buf;
+
+  if (nghttp2_buf_avail(buf) < (ssize_t)frame->hd.length) {
+    return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+  }
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  buf->last +=
+      nghttp2_frame_pack_settings_payload(buf->last, frame->iv, frame->niv);
+
+  return 0;
+}
+
+size_t nghttp2_frame_pack_settings_payload(uint8_t *buf,
+                                           const nghttp2_settings_entry *iv,
+                                           size_t niv) {
+  size_t i;
+  for (i = 0; i < niv; ++i, buf += NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) {
+    nghttp2_put_uint16be(buf, iv[i].settings_id);
+    nghttp2_put_uint32be(buf + 2, iv[i].value);
+  }
+  return NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH * niv;
+}
+
+int nghttp2_frame_unpack_settings_payload(nghttp2_settings *frame,
+                                          nghttp2_settings_entry *iv,
+                                          size_t niv, nghttp2_mem *mem) {
+  size_t payloadlen = niv * sizeof(nghttp2_settings_entry);
+
+  if (niv == 0) {
+    frame->iv = NULL;
+  } else {
+    frame->iv = nghttp2_mem_malloc(mem, payloadlen);
+
+    if (frame->iv == NULL) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+
+    memcpy(frame->iv, iv, payloadlen);
+  }
+
+  frame->niv = niv;
+  return 0;
+}
+
+void nghttp2_frame_unpack_settings_entry(nghttp2_settings_entry *iv,
+                                         const uint8_t *payload) {
+  iv->settings_id = nghttp2_get_uint16(&payload[0]);
+  iv->value = nghttp2_get_uint32(&payload[2]);
+}
+
+int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr,
+                                           size_t *niv_ptr,
+                                           const uint8_t *payload,
+                                           size_t payloadlen,
+                                           nghttp2_mem *mem) {
+  size_t i;
+
+  *niv_ptr = payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH;
+
+  if (*niv_ptr == 0) {
+    *iv_ptr = NULL;
+
+    return 0;
+  }
+
+  *iv_ptr =
+      nghttp2_mem_malloc(mem, (*niv_ptr) * sizeof(nghttp2_settings_entry));
+
+  if (*iv_ptr == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  for (i = 0; i < *niv_ptr; ++i) {
+    size_t off = i * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH;
+    nghttp2_frame_unpack_settings_entry(&(*iv_ptr)[i], &payload[off]);
+  }
+
+  return 0;
+}
+
+int nghttp2_frame_pack_push_promise(nghttp2_bufs *bufs,
+                                    nghttp2_push_promise *frame,
+                                    nghttp2_hd_deflater *deflater) {
+  size_t nv_offset = 4;
+  int rv;
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->cur->buf;
+
+  buf->pos += nv_offset;
+  buf->last = buf->pos;
+
+  /* This call will adjust buf->last to the correct position */
+  rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, frame->nva, frame->nvlen);
+
+  if (rv == NGHTTP2_ERR_BUFFER_ERROR) {
+    rv = NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  buf->pos -= nv_offset;
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  nghttp2_put_uint32be(buf->pos, frame->promised_stream_id);
+
+  frame->padlen = 0;
+  frame->hd.length = nghttp2_bufs_len(bufs);
+
+  return frame_pack_headers_shared(bufs, &frame->hd);
+}
+
+int nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame,
+                                              const uint8_t *payload,
+                                              size_t payloadlen _U_) {
+  frame->promised_stream_id =
+      nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK;
+  frame->nva = NULL;
+  frame->nvlen = 0;
+  return 0;
+}
+
+int nghttp2_frame_pack_ping(nghttp2_bufs *bufs, nghttp2_ping *frame) {
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->head->buf;
+
+  assert(nghttp2_buf_avail(buf) >= 8);
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  buf->last =
+      nghttp2_cpymem(buf->last, frame->opaque_data, sizeof(frame->opaque_data));
+
+  return 0;
+}
+
+void nghttp2_frame_unpack_ping_payload(nghttp2_ping *frame,
+                                       const uint8_t *payload,
+                                       size_t payloadlen _U_) {
+  memcpy(frame->opaque_data, payload, sizeof(frame->opaque_data));
+}
+
+int nghttp2_frame_pack_goaway(nghttp2_bufs *bufs, nghttp2_goaway *frame) {
+  int rv;
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->head->buf;
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  nghttp2_put_uint32be(buf->last, frame->last_stream_id);
+  buf->last += 4;
+
+  nghttp2_put_uint32be(buf->last, frame->error_code);
+  buf->last += 4;
+
+  rv = nghttp2_bufs_add(bufs, frame->opaque_data, frame->opaque_data_len);
+
+  if (rv == NGHTTP2_ERR_BUFFER_ERROR) {
+    return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+  }
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+void nghttp2_frame_unpack_goaway_payload(nghttp2_goaway *frame,
+                                         const uint8_t *payload,
+                                         size_t payloadlen _U_,
+                                         uint8_t *var_gift_payload,
+                                         size_t var_gift_payloadlen) {
+  frame->last_stream_id = nghttp2_get_uint32(payload) & NGHTTP2_STREAM_ID_MASK;
+  frame->error_code = nghttp2_get_uint32(payload + 4);
+
+  frame->opaque_data = var_gift_payload;
+  frame->opaque_data_len = var_gift_payloadlen;
+}
+
+int nghttp2_frame_unpack_goaway_payload2(nghttp2_goaway *frame,
+                                         const uint8_t *payload,
+                                         size_t payloadlen, nghttp2_mem *mem) {
+  uint8_t *var_gift_payload;
+  size_t var_gift_payloadlen;
+
+  if (payloadlen > 8) {
+    var_gift_payloadlen = payloadlen - 8;
+  } else {
+    var_gift_payloadlen = 0;
+  }
+
+  payloadlen -= var_gift_payloadlen;
+
+  if (!var_gift_payloadlen) {
+    var_gift_payload = NULL;
+  } else {
+    var_gift_payload = nghttp2_mem_malloc(mem, var_gift_payloadlen);
+
+    if (var_gift_payload == NULL) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+
+    memcpy(var_gift_payload, payload + 8, var_gift_payloadlen);
+  }
+
+  nghttp2_frame_unpack_goaway_payload(frame, payload, payloadlen,
+                                      var_gift_payload, var_gift_payloadlen);
+
+  return 0;
+}
+
+int nghttp2_frame_pack_window_update(nghttp2_bufs *bufs,
+                                     nghttp2_window_update *frame) {
+  nghttp2_buf *buf;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->head->buf;
+
+  assert(nghttp2_buf_avail(buf) >= 4);
+
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  nghttp2_put_uint32be(buf->last, frame->window_size_increment);
+  buf->last += 4;
+
+  return 0;
+}
+
+void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
+                                                const uint8_t *payload,
+                                                size_t payloadlen _U_) {
+  frame->window_size_increment =
+      nghttp2_get_uint32(payload) & NGHTTP2_WINDOW_SIZE_INCREMENT_MASK;
+}
+
+nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv,
+                                              size_t niv, nghttp2_mem *mem) {
+  nghttp2_settings_entry *iv_copy;
+  size_t len = niv * sizeof(nghttp2_settings_entry);
+
+  if (len == 0) {
+    return NULL;
+  }
+
+  iv_copy = nghttp2_mem_malloc(mem, len);
+
+  if (iv_copy == NULL) {
+    return NULL;
+  }
+
+  memcpy(iv_copy, iv, len);
+
+  return iv_copy;
+}
+
+int nghttp2_nv_equal(const nghttp2_nv *a, const nghttp2_nv *b) {
+  return a->namelen == b->namelen && a->valuelen == b->valuelen &&
+         memcmp(a->name, b->name, a->namelen) == 0 &&
+         memcmp(a->value, b->value, a->valuelen) == 0;
+}
+
+void nghttp2_nv_array_del(nghttp2_nv *nva, nghttp2_mem *mem) {
+  nghttp2_mem_free(mem, nva);
+}
+
+static int bytes_compar(const uint8_t *a, size_t alen, const uint8_t *b,
+                        size_t blen) {
+  int rv;
+
+  if (alen == blen) {
+    return memcmp(a, b, alen);
+  }
+
+  if (alen < blen) {
+    rv = memcmp(a, b, alen);
+
+    if (rv == 0) {
+      return -1;
+    }
+
+    return rv;
+  }
+
+  rv = memcmp(a, b, blen);
+
+  if (rv == 0) {
+    return 1;
+  }
+
+  return rv;
+}
+
+int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs) {
+  return bytes_compar(lhs->name, lhs->namelen, rhs->name, rhs->namelen);
+}
+
+static int nv_compar(const void *lhs, const void *rhs) {
+  const nghttp2_nv *a = (const nghttp2_nv *)lhs;
+  const nghttp2_nv *b = (const nghttp2_nv *)rhs;
+  int rv;
+
+  rv = bytes_compar(a->name, a->namelen, b->name, b->namelen);
+
+  if (rv == 0) {
+    return bytes_compar(a->value, a->valuelen, b->value, b->valuelen);
+  }
+
+  return rv;
+}
+
+void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen) {
+  qsort(nva, nvlen, sizeof(nghttp2_nv), nv_compar);
+}
+
+int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva,
+                          size_t nvlen, nghttp2_mem *mem) {
+  size_t i;
+  uint8_t *data;
+  size_t buflen = 0;
+  nghttp2_nv *p;
+
+  for (i = 0; i < nvlen; ++i) {
+    buflen += nva[i].namelen + nva[i].valuelen;
+  }
+
+  if (nvlen == 0) {
+    *nva_ptr = NULL;
+
+    return 0;
+  }
+
+  buflen += sizeof(nghttp2_nv) * nvlen;
+
+  *nva_ptr = nghttp2_mem_malloc(mem, buflen);
+
+  if (*nva_ptr == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  p = *nva_ptr;
+  data = (uint8_t *)(*nva_ptr) + sizeof(nghttp2_nv) * nvlen;
+
+  for (i = 0; i < nvlen; ++i) {
+    p->flags = nva[i].flags;
+
+    memcpy(data, nva[i].name, nva[i].namelen);
+    p->name = data;
+    p->namelen = nva[i].namelen;
+    nghttp2_downcase(p->name, p->namelen);
+    data += nva[i].namelen;
+    memcpy(data, nva[i].value, nva[i].valuelen);
+    p->value = data;
+    p->valuelen = nva[i].valuelen;
+    data += nva[i].valuelen;
+    ++p;
+  }
+  return 0;
+}
+
+int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) {
+  size_t i;
+  for (i = 0; i < niv; ++i) {
+    switch (iv[i].settings_id) {
+    case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+      if (iv[i].value > NGHTTP2_MAX_HEADER_TABLE_SIZE) {
+        return 0;
+      }
+      break;
+    case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+      break;
+    case NGHTTP2_SETTINGS_ENABLE_PUSH:
+      if (iv[i].value != 0 && iv[i].value != 1) {
+        return 0;
+      }
+      break;
+    case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+      if (iv[i].value > (uint32_t)NGHTTP2_MAX_WINDOW_SIZE) {
+        return 0;
+      }
+      break;
+    case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+      if (iv[i].value < NGHTTP2_MAX_FRAME_SIZE_MIN ||
+          iv[i].value > NGHTTP2_MAX_FRAME_SIZE_MAX) {
+        return 0;
+      }
+      break;
+    case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+      break;
+    }
+  }
+  return 1;
+}
+
+static void frame_set_pad(nghttp2_buf *buf, size_t padlen) {
+  size_t trail_padlen;
+  size_t newlen;
+
+  DEBUGF(fprintf(stderr, "send: padlen=%zu, shift left 1 bytes\n", padlen));
+
+  memmove(buf->pos - 1, buf->pos, NGHTTP2_FRAME_HDLEN);
+
+  --buf->pos;
+
+  buf->pos[4] |= NGHTTP2_FLAG_PADDED;
+
+  newlen = (nghttp2_get_uint32(buf->pos) >> 8) + padlen;
+  nghttp2_put_uint32be(buf->pos, (uint32_t)((newlen << 8) + buf->pos[3]));
+
+  trail_padlen = padlen - 1;
+  buf->pos[NGHTTP2_FRAME_HDLEN] = trail_padlen;
+
+  /* zero out padding */
+  memset(buf->last, 0, trail_padlen);
+  /* extend buffers trail_padlen bytes, since we ate previous padlen -
+     trail_padlen byte(s) */
+  buf->last += trail_padlen;
+
+  return;
+}
+
+int nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd,
+                          size_t padlen) {
+  nghttp2_buf *buf;
+
+  if (padlen == 0) {
+    DEBUGF(fprintf(stderr, "send: padlen = 0, nothing to do\n"));
+
+    return 0;
+  }
+
+  /*
+   * We have arranged bufs like this:
+   *
+   *  0                   1                   2                   3
+   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   * | |Frame header     | Frame payload...                          :
+   * +-+-----------------+-------------------------------------------+
+   * | |Frame header     | Frame payload...                          :
+   * +-+-----------------+-------------------------------------------+
+   * | |Frame header     | Frame payload...                          :
+   * +-+-----------------+-------------------------------------------+
+   *
+   * We arranged padding so that it is included in the first frame
+   * completely.  For padded frame, we are going to adjust buf->pos of
+   * frame which includes padding and serialize (memmove) frame header
+   * in the correct position.  Also extends buf->last to include
+   * padding.
+   */
+
+  buf = &bufs->head->buf;
+
+  assert(nghttp2_buf_avail(buf) >= (ssize_t)padlen - 1);
+
+  frame_set_pad(buf, padlen);
+
+  hd->length += padlen;
+  hd->flags |= NGHTTP2_FLAG_PADDED;
+
+  DEBUGF(fprintf(stderr, "send: final payloadlen=%zu, padlen=%zu\n", hd->length,
+                 padlen));
+
+  return 0;
+}
diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h
new file mode 100644 (file)
index 0000000..4304b19
--- /dev/null
@@ -0,0 +1,526 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_FRAME_H
+#define NGHTTP2_FRAME_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_hd.h"
+#include "nghttp2_buf.h"
+
+#define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1)
+#define NGHTTP2_PRI_GROUP_ID_MASK ((1u << 31) - 1)
+#define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1)
+#define NGHTTP2_WINDOW_SIZE_INCREMENT_MASK ((1u << 31) - 1)
+#define NGHTTP2_SETTINGS_ID_MASK ((1 << 24) - 1)
+
+/* The number of bytes of frame header. */
+#define NGHTTP2_FRAME_HDLEN 9
+
+#define NGHTTP2_MAX_FRAME_SIZE_MAX ((1 << 24) - 1)
+#define NGHTTP2_MAX_FRAME_SIZE_MIN (1 << 14)
+
+#define NGHTTP2_MAX_PAYLOADLEN 16384
+/* The one frame buffer length for tranmission.  We may use several of
+   them to support CONTINUATION.  To account for Pad Length field, we
+   allocate extra 1 byte, which saves extra large memcopying. */
+#define NGHTTP2_FRAMEBUF_CHUNKLEN                                              \
+  (NGHTTP2_FRAME_HDLEN + 1 + NGHTTP2_MAX_PAYLOADLEN)
+
+/* Number of inbound buffer */
+#define NGHTTP2_FRAMEBUF_MAX_NUM 5
+
+/* The default length of DATA frame payload. This should be small enough
+ * for the data payload and the header to fit into 1 TLS record */
+#define NGHTTP2_DATA_PAYLOADLEN                                                \
+  ((NGHTTP2_MAX_FRAME_SIZE_MIN) - (NGHTTP2_FRAME_HDLEN))
+
+/* Maximum headers payload length, calculated in compressed form.
+   This applies to transmission only. */
+#define NGHTTP2_MAX_HEADERSLEN 65536
+
+/* The number of bytes for each SETTINGS entry */
+#define NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH 6
+
+/* The maximum header table size in SETTINGS_HEADER_TABLE_SIZE */
+#define NGHTTP2_MAX_HEADER_TABLE_SIZE ((1u << 31) - 1)
+
+/* Length of priority related fields in HEADERS/PRIORITY frames */
+#define NGHTTP2_PRIORITY_SPECLEN 5
+
+/* Maximum length of padding in bytes. */
+#define NGHTTP2_MAX_PADLEN 256
+
+/* Union of extension frame payload */
+typedef union { nghttp2_ext_altsvc altsvc; } nghttp2_ext_frame_payload;
+
+void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd);
+
+void nghttp2_frame_unpack_frame_hd(nghttp2_frame_hd *hd, const uint8_t *buf);
+
+/**
+ * Initializes frame header |hd| with given parameters.  Reserved bit
+ * is set to 0.
+ */
+void nghttp2_frame_hd_init(nghttp2_frame_hd *hd, size_t length, uint8_t type,
+                           uint8_t flags, int32_t stream_id);
+
+/**
+ * Returns the number of priority field depending on the |flags|.  If
+ * |flags| has neither NGHTTP2_FLAG_PRIORITY_GROUP nor
+ * NGHTTP2_FLAG_PRIORITY_DEPENDENCY set, return 0.
+ */
+size_t nghttp2_frame_priority_len(uint8_t flags);
+
+/**
+ * Packs the |pri_spec| in |buf|.  This function assumes |buf| has
+ * enough space for serialization.
+ */
+void nghttp2_frame_pack_priority_spec(uint8_t *buf,
+                                      const nghttp2_priority_spec *pri_spec);
+
+/**
+ * Unpacks the priority specification from payload |payload| of length
+ * |payloadlen| to |pri_spec|.  The |flags| is used to determine what
+ * kind of priority specification is in |payload|.  This function
+ * assumes the |payload| contains whole priority specification.
+ */
+void nghttp2_frame_unpack_priority_spec(nghttp2_priority_spec *pri_spec,
+                                        uint8_t flags, const uint8_t *payload,
+                                        size_t payloadlen);
+
+/*
+ * Returns the offset from the HEADERS frame payload where the
+ * compressed header block starts. The frame payload does not include
+ * frame header.
+ */
+size_t nghttp2_frame_headers_payload_nv_offset(nghttp2_headers *frame);
+
+/*
+ * Packs HEADERS frame |frame| in wire format and store it in |bufs|.
+ * This function expands |bufs| as necessary to store frame.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * frame->hd.length is assigned after length is determined during
+ * packing process.  CONTINUATION frames are also serialized in this
+ * function. This function does not handle padding.
+ *
+ * This function returns 0 if it succeeds, or returns one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_HEADER_COMP
+ *     The deflate operation failed.
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_frame_pack_headers(nghttp2_bufs *bufs, nghttp2_headers *frame,
+                               nghttp2_hd_deflater *deflater);
+
+/*
+ * Unpacks HEADERS frame byte sequence into |frame|.  This function
+ * only unapcks bytes that come before name/value header block and
+ * after possible Pad Length field.
+ *
+ * This function always succeeds and returns 0.
+ */
+int nghttp2_frame_unpack_headers_payload(nghttp2_headers *frame,
+                                         const uint8_t *payload,
+                                         size_t payloadlen);
+
+/*
+ * Packs PRIORITY frame |frame| in wire format and store it in
+ * |bufs|.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * This function always succeeds and returns 0.
+ */
+int nghttp2_frame_pack_priority(nghttp2_bufs *bufs, nghttp2_priority *frame);
+
+/*
+ * Unpacks PRIORITY wire format into |frame|.
+ */
+void nghttp2_frame_unpack_priority_payload(nghttp2_priority *frame,
+                                           const uint8_t *payload,
+                                           size_t payloadlen);
+
+/*
+ * Packs RST_STREAM frame |frame| in wire frame format and store it in
+ * |bufs|.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * This function always succeeds and returns 0.
+ */
+int nghttp2_frame_pack_rst_stream(nghttp2_bufs *bufs,
+                                  nghttp2_rst_stream *frame);
+
+/*
+ * Unpacks RST_STREAM frame byte sequence into |frame|.
+ */
+void nghttp2_frame_unpack_rst_stream_payload(nghttp2_rst_stream *frame,
+                                             const uint8_t *payload,
+                                             size_t payloadlen);
+
+/*
+ * Packs SETTINGS frame |frame| in wire format and store it in
+ * |bufs|.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * This function returns 0 if it succeeds, or returns one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_FRAME_SIZE_ERROR
+ *     The length of the frame is too large.
+ */
+int nghttp2_frame_pack_settings(nghttp2_bufs *bufs, nghttp2_settings *frame);
+
+/*
+ * Packs the |iv|, which includes |niv| entries, in the |buf|,
+ * assuming the |buf| has at least 8 * |niv| bytes.
+ *
+ * Returns the number of bytes written into the |buf|.
+ */
+size_t nghttp2_frame_pack_settings_payload(uint8_t *buf,
+                                           const nghttp2_settings_entry *iv,
+                                           size_t niv);
+
+void nghttp2_frame_unpack_settings_entry(nghttp2_settings_entry *iv,
+                                         const uint8_t *payload);
+
+/*
+ * Makes a copy of |iv| in frame->settings.iv. The |niv| is assigned
+ * to frame->settings.niv.
+ *
+ * This function returns 0 if it succeeds or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_frame_unpack_settings_payload(nghttp2_settings *frame,
+                                          nghttp2_settings_entry *iv,
+                                          size_t niv, nghttp2_mem *mem);
+
+/*
+ * Unpacks SETTINGS payload into |*iv_ptr|. The number of entries are
+ * assigned to the |*niv_ptr|. This function allocates enough memory
+ * to store the result in |*iv_ptr|. The caller is responsible to free
+ * |*iv_ptr| after its use.
+ *
+ * This function returns 0 if it succeeds or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_frame_unpack_settings_payload2(nghttp2_settings_entry **iv_ptr,
+                                           size_t *niv_ptr,
+                                           const uint8_t *payload,
+                                           size_t payloadlen, nghttp2_mem *mem);
+
+/*
+ * Packs PUSH_PROMISE frame |frame| in wire format and store it in
+ * |bufs|.  This function expands |bufs| as necessary to store
+ * frame.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * frame->hd.length is assigned after length is determined during
+ * packing process.  CONTINUATION frames are also serialized in this
+ * function. This function does not handle padding.
+ *
+ * This function returns 0 if it succeeds, or returns one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_HEADER_COMP
+ *     The deflate operation failed.
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_frame_pack_push_promise(nghttp2_bufs *bufs,
+                                    nghttp2_push_promise *frame,
+                                    nghttp2_hd_deflater *deflater);
+
+/*
+ * Unpacks PUSH_PROMISE frame byte sequence into |frame|.  This
+ * function only unapcks bytes that come before name/value header
+ * block and after possible Pad Length field.
+ *
+ * This function returns 0 if it succeeds or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_PROTO
+ *     TODO END_HEADERS flag is not set
+ */
+int nghttp2_frame_unpack_push_promise_payload(nghttp2_push_promise *frame,
+                                              const uint8_t *payload,
+                                              size_t payloadlen);
+
+/*
+ * Packs PING frame |frame| in wire format and store it in
+ * |bufs|.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * This function always succeeds and returns 0.
+ */
+int nghttp2_frame_pack_ping(nghttp2_bufs *bufs, nghttp2_ping *frame);
+
+/*
+ * Unpacks PING wire format into |frame|.
+ */
+void nghttp2_frame_unpack_ping_payload(nghttp2_ping *frame,
+                                       const uint8_t *payload,
+                                       size_t payloadlen);
+
+/*
+ * Packs GOAWAY frame |frame| in wire format and store it in |bufs|.
+ * This function expands |bufs| as necessary to store frame.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * This function returns 0 if it succeeds or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_FRAME_SIZE_ERROR
+ *     The length of the frame is too large.
+ */
+int nghttp2_frame_pack_goaway(nghttp2_bufs *bufs, nghttp2_goaway *frame);
+
+/*
+ * Unpacks GOAWAY wire format into |frame|.  The |payload| of length
+ * |payloadlen| contains first 8 bytes of payload.  The
+ * |var_gift_payload| of length |var_gift_payloadlen| contains
+ * remaining payload and its buffer is gifted to the function and then
+ * |frame|.  The |var_gift_payloadlen| must be freed by
+ * nghttp2_frame_goaway_free().
+ */
+void nghttp2_frame_unpack_goaway_payload(nghttp2_goaway *frame,
+                                         const uint8_t *payload,
+                                         size_t payloadlen,
+                                         uint8_t *var_gift_payload,
+                                         size_t var_gift_payloadlen);
+
+/*
+ * Unpacks GOAWAY wire format into |frame|.  This function only exists
+ * for unit test.  After allocating buffer for debug data, this
+ * function internally calls nghttp2_frame_unpack_goaway_payload().
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_frame_unpack_goaway_payload2(nghttp2_goaway *frame,
+                                         const uint8_t *payload,
+                                         size_t payloadlen, nghttp2_mem *mem);
+
+/*
+ * Packs WINDOW_UPDATE frame |frame| in wire frame format and store it
+ * in |bufs|.
+ *
+ * The caller must make sure that nghttp2_bufs_reset(bufs) is called
+ * before calling this function.
+ *
+ * This function always succeeds and returns 0.
+ */
+int nghttp2_frame_pack_window_update(nghttp2_bufs *bufs,
+                                     nghttp2_window_update *frame);
+
+/*
+ * Unpacks WINDOW_UPDATE frame byte sequence into |frame|.
+ */
+void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame,
+                                                const uint8_t *payload,
+                                                size_t payloadlen);
+
+/*
+ * Initializes HEADERS frame |frame| with given values.  |frame| takes
+ * ownership of |nva|, so caller must not free it. If |stream_id| is
+ * not assigned yet, it must be -1.
+ */
+void nghttp2_frame_headers_init(nghttp2_headers *frame, uint8_t flags,
+                                int32_t stream_id, nghttp2_headers_category cat,
+                                const nghttp2_priority_spec *pri_spec,
+                                nghttp2_nv *nva, size_t nvlen);
+
+void nghttp2_frame_headers_free(nghttp2_headers *frame, nghttp2_mem *mem);
+
+void nghttp2_frame_priority_init(nghttp2_priority *frame, int32_t stream_id,
+                                 const nghttp2_priority_spec *pri_spec);
+
+void nghttp2_frame_priority_free(nghttp2_priority *frame);
+
+void nghttp2_frame_rst_stream_init(nghttp2_rst_stream *frame, int32_t stream_id,
+                                   uint32_t error_code);
+
+void nghttp2_frame_rst_stream_free(nghttp2_rst_stream *frame);
+
+/*
+ * Initializes PUSH_PROMISE frame |frame| with given values.  |frame|
+ * takes ownership of |nva|, so caller must not free it.
+ */
+void nghttp2_frame_push_promise_init(nghttp2_push_promise *frame, uint8_t flags,
+                                     int32_t stream_id,
+                                     int32_t promised_stream_id,
+                                     nghttp2_nv *nva, size_t nvlen);
+
+void nghttp2_frame_push_promise_free(nghttp2_push_promise *frame,
+                                     nghttp2_mem *mem);
+
+/*
+ * Initializes SETTINGS frame |frame| with given values. |frame| takes
+ * ownership of |iv|, so caller must not free it. The |flags| are
+ * bitwise-OR of one or more of nghttp2_settings_flag.
+ */
+void nghttp2_frame_settings_init(nghttp2_settings *frame, uint8_t flags,
+                                 nghttp2_settings_entry *iv, size_t niv);
+
+void nghttp2_frame_settings_free(nghttp2_settings *frame, nghttp2_mem *mem);
+
+/*
+ * Initializes PING frame |frame| with given values. If the
+ * |opqeue_data| is not NULL, it must point to 8 bytes memory region
+ * of data. The data pointed by |opaque_data| is copied. It can be
+ * NULL. In this case, 8 bytes NULL is used.
+ */
+void nghttp2_frame_ping_init(nghttp2_ping *frame, uint8_t flags,
+                             const uint8_t *opque_data);
+
+void nghttp2_frame_ping_free(nghttp2_ping *frame);
+
+/*
+ * Initializes GOAWAY frame |frame| with given values. On success,
+ * this function takes ownership of |opaque_data|, so caller must not
+ * free it. If the |opaque_data_len| is 0, opaque_data could be NULL.
+ */
+void nghttp2_frame_goaway_init(nghttp2_goaway *frame, int32_t last_stream_id,
+                               uint32_t error_code, uint8_t *opaque_data,
+                               size_t opaque_data_len);
+
+void nghttp2_frame_goaway_free(nghttp2_goaway *frame, nghttp2_mem *mem);
+
+void nghttp2_frame_window_update_init(nghttp2_window_update *frame,
+                                      uint8_t flags, int32_t stream_id,
+                                      int32_t window_size_increment);
+
+void nghttp2_frame_window_update_free(nghttp2_window_update *frame);
+
+/*
+ * Returns the number of padding bytes after payload.  The total
+ * padding length is given in the |padlen|.  The returned value does
+ * not include the Pad Length field.
+ */
+size_t nghttp2_frame_trail_padlen(nghttp2_frame *frame, size_t padlen);
+
+void nghttp2_frame_data_init(nghttp2_data *frame, uint8_t flags,
+                             int32_t stream_id);
+
+void nghttp2_frame_data_free(nghttp2_data *frame);
+
+/*
+ * Makes copy of |iv| and return the copy. The |niv| is the number of
+ * entries in |iv|. This function returns the pointer to the copy if
+ * it succeeds, or NULL.
+ */
+nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv,
+                                              size_t niv, nghttp2_mem *mem);
+
+/*
+ * Sorts the |nva| in ascending order of name and value. If names are
+ * equivalent, sort them by value.
+ */
+void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen);
+
+/*
+ * Copies name/value pairs from |nva|, which contains |nvlen| pairs,
+ * to |*nva_ptr|, which is dynamically allocated so that all items can
+ * be stored.
+ *
+ * The |*nva_ptr| must be freed using nghttp2_nv_array_del().
+ *
+ * This function returns 0 if it succeeds or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_nv_array_copy(nghttp2_nv **nva_ptr, const nghttp2_nv *nva,
+                          size_t nvlen, nghttp2_mem *mem);
+
+/*
+ * Returns nonzero if the name/value pair |a| equals to |b|. The name
+ * is compared in case-sensitive, because we ensure that this function
+ * is called after the name is lower-cased.
+ */
+int nghttp2_nv_equal(const nghttp2_nv *a, const nghttp2_nv *b);
+
+/*
+ * Frees |nva|.
+ */
+void nghttp2_nv_array_del(nghttp2_nv *nva, nghttp2_mem *mem);
+
+/*
+ * Checks that the |iv|, which includes |niv| entries, does not have
+ * invalid values.
+ *
+ * This function returns nonzero if it succeeds, or 0.
+ */
+int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv);
+
+/*
+ * Sets Pad Length field and flags and adjusts frame header position
+ * of each buffers in |bufs|.  The number of padding is given in the
+ * |padlen| including Pad Length field.  The |hd| is the frame header
+ * for the serialized data.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_FRAME_SIZE_ERROR
+ *     The length of the resulting frame is too large.
+ */
+int nghttp2_frame_add_pad(nghttp2_bufs *bufs, nghttp2_frame_hd *hd,
+                          size_t padlen);
+
+#endif /* NGHTTP2_FRAME_H */
diff --git a/lib/nghttp2_hd.c b/lib/nghttp2_hd.c
new file mode 100644 (file)
index 0000000..6e5662b
--- /dev/null
@@ -0,0 +1,1925 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_hd.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "nghttp2_helper.h"
+#include "nghttp2_int.h"
+
+#define STATIC_TABLE_LENGTH 61
+
+/* Make scalar initialization form of nghttp2_nv */
+#define MAKE_STATIC_ENT(I, N, V, NH, VH)                                       \
+  {                                                                            \
+    {                                                                          \
+      { (uint8_t *) N, (uint8_t *)V, sizeof(N) - 1, sizeof(V) - 1, 0 }         \
+      , NH, VH, 1, NGHTTP2_HD_FLAG_NONE                                        \
+    }                                                                          \
+    , I                                                                        \
+  }
+
+/* Generated by mkstatictbl.py */
+/* Sorted by hash(name) and its table index */
+static nghttp2_hd_static_entry static_table[] = {
+    MAKE_STATIC_ENT(20, "age", "", 96511u, 0u),
+    MAKE_STATIC_ENT(59, "via", "", 116750u, 0u),
+    MAKE_STATIC_ENT(32, "date", "", 3076014u, 0u),
+    MAKE_STATIC_ENT(33, "etag", "", 3123477u, 0u),
+    MAKE_STATIC_ENT(36, "from", "", 3151786u, 0u),
+    MAKE_STATIC_ENT(37, "host", "", 3208616u, 0u),
+    MAKE_STATIC_ENT(44, "link", "", 3321850u, 0u),
+    MAKE_STATIC_ENT(58, "vary", "", 3612210u, 0u),
+    MAKE_STATIC_ENT(38, "if-match", "", 34533653u, 0u),
+    MAKE_STATIC_ENT(41, "if-range", "", 39145613u, 0u),
+    MAKE_STATIC_ENT(3, ":path", "/", 56997727u, 47u),
+    MAKE_STATIC_ENT(4, ":path", "/index.html", 56997727u, 2144181430u),
+    MAKE_STATIC_ENT(21, "allow", "", 92906313u, 0u),
+    MAKE_STATIC_ENT(49, "range", "", 108280125u, 0u),
+    MAKE_STATIC_ENT(14, "accept-charset", "", 124285319u, 0u),
+    MAKE_STATIC_ENT(43, "last-modified", "", 150043680u, 0u),
+    MAKE_STATIC_ENT(48, "proxy-authorization", "", 329532250u, 0u),
+    MAKE_STATIC_ENT(57, "user-agent", "", 486342275u, 0u),
+    MAKE_STATIC_ENT(40, "if-none-match", "", 646073760u, 0u),
+    MAKE_STATIC_ENT(30, "content-type", "", 785670158u, 0u),
+    MAKE_STATIC_ENT(16, "accept-language", "", 802785917u, 0u),
+    MAKE_STATIC_ENT(50, "referer", "", 1085069613u, 0u),
+    MAKE_STATIC_ENT(51, "refresh", "", 1085444827u, 0u),
+    MAKE_STATIC_ENT(55, "strict-transport-security", "", 1153852136u, 0u),
+    MAKE_STATIC_ENT(54, "set-cookie", "", 1237214767u, 0u),
+    MAKE_STATIC_ENT(56, "transfer-encoding", "", 1274458357u, 0u),
+    MAKE_STATIC_ENT(17, "accept-ranges", "", 1397189435u, 0u),
+    MAKE_STATIC_ENT(42, "if-unmodified-since", "", 1454068927u, 0u),
+    MAKE_STATIC_ENT(46, "max-forwards", "", 1619948695u, 0u),
+    MAKE_STATIC_ENT(45, "location", "", 1901043637u, 0u),
+    MAKE_STATIC_ENT(52, "retry-after", "", 1933352567u, 0u),
+    MAKE_STATIC_ENT(25, "content-encoding", "", 2095084583u, 0u),
+    MAKE_STATIC_ENT(28, "content-location", "", 2284906121u, 0u),
+    MAKE_STATIC_ENT(39, "if-modified-since", "", 2302095846u, 0u),
+    MAKE_STATIC_ENT(18, "accept", "", 2871506184u, 0u),
+    MAKE_STATIC_ENT(29, "content-range", "", 2878374633u, 0u),
+    MAKE_STATIC_ENT(22, "authorization", "", 2909397113u, 0u),
+    MAKE_STATIC_ENT(31, "cookie", "", 2940209764u, 0u),
+    MAKE_STATIC_ENT(0, ":authority", "", 2962729033u, 0u),
+    MAKE_STATIC_ENT(35, "expires", "", 2985731892u, 0u),
+    MAKE_STATIC_ENT(34, "expect", "", 3005803609u, 0u),
+    MAKE_STATIC_ENT(24, "content-disposition", "", 3027699811u, 0u),
+    MAKE_STATIC_ENT(26, "content-language", "", 3065240108u, 0u),
+    MAKE_STATIC_ENT(1, ":method", "GET", 3153018267u, 70454u),
+    MAKE_STATIC_ENT(2, ":method", "POST", 3153018267u, 2461856u),
+    MAKE_STATIC_ENT(27, "content-length", "", 3162187450u, 0u),
+    MAKE_STATIC_ENT(19, "access-control-allow-origin", "", 3297999203u, 0u),
+    MAKE_STATIC_ENT(5, ":scheme", "http", 3322585695u, 3213448u),
+    MAKE_STATIC_ENT(6, ":scheme", "https", 3322585695u, 99617003u),
+    MAKE_STATIC_ENT(7, ":status", "200", 3338091692u, 49586u),
+    MAKE_STATIC_ENT(8, ":status", "204", 3338091692u, 49590u),
+    MAKE_STATIC_ENT(9, ":status", "206", 3338091692u, 49592u),
+    MAKE_STATIC_ENT(10, ":status", "304", 3338091692u, 50551u),
+    MAKE_STATIC_ENT(11, ":status", "400", 3338091692u, 51508u),
+    MAKE_STATIC_ENT(12, ":status", "404", 3338091692u, 51512u),
+    MAKE_STATIC_ENT(13, ":status", "500", 3338091692u, 52469u),
+    MAKE_STATIC_ENT(53, "server", "", 3389140803u, 0u),
+    MAKE_STATIC_ENT(47, "proxy-authenticate", "", 3993199572u, 0u),
+    MAKE_STATIC_ENT(60, "www-authenticate", "", 4051929931u, 0u),
+    MAKE_STATIC_ENT(23, "cache-control", "", 4086191634u, 0u),
+    MAKE_STATIC_ENT(15, "accept-encoding", "gzip, deflate", 4127597688u,
+                    1733326877u),
+};
+
+/* Index to the position in static_table */
+const size_t static_table_index[] = {
+    38, 43, 44, 10, 11, 47, 48, 49, 50, 51, 52, 53, 54, 55, 14, 60,
+    20, 26, 34, 46, 0,  12, 36, 59, 41, 31, 42, 45, 32, 35, 19, 37,
+    2,  3,  40, 39, 4,  5,  8,  33, 18, 9,  27, 15, 6,  29, 28, 57,
+    16, 13, 21, 22, 30, 56, 24, 23, 25, 17, 7,  1,  58};
+
+const size_t NGHTTP2_STATIC_TABLE_LENGTH =
+    sizeof(static_table) / sizeof(static_table[0]);
+
+static int memeq(const void *s1, const void *s2, size_t n) {
+  const uint8_t *a = (const uint8_t *)s1, *b = (const uint8_t *)s2;
+  uint8_t c = 0;
+  while (n > 0) {
+    c |= (*a++) ^ (*b++);
+    --n;
+  }
+  return c == 0;
+}
+
+static uint32_t hash(const uint8_t *s, size_t n) {
+  uint32_t h = 0;
+  while (n > 0) {
+    h = h * 31 + *s++;
+    --n;
+  }
+  return h;
+}
+
+int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name,
+                          size_t namelen, uint8_t *value, size_t valuelen,
+                          uint32_t name_hash, uint32_t value_hash,
+                          nghttp2_mem *mem) {
+  int rv = 0;
+
+  /* Since nghttp2_hd_entry is used for indexing, ent->nv.flags always
+     NGHTTP2_NV_FLAG_NONE */
+  ent->nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+  if ((flags & NGHTTP2_HD_FLAG_NAME_ALLOC) &&
+      (flags & NGHTTP2_HD_FLAG_NAME_GIFT) == 0) {
+    if (namelen == 0) {
+      /* We should not allow empty header field name */
+      ent->nv.name = NULL;
+    } else {
+      ent->nv.name = nghttp2_memdup(name, namelen, mem);
+      if (ent->nv.name == NULL) {
+        rv = NGHTTP2_ERR_NOMEM;
+        goto fail;
+      }
+    }
+  } else {
+    ent->nv.name = name;
+  }
+  if ((flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) &&
+      (flags & NGHTTP2_HD_FLAG_VALUE_GIFT) == 0) {
+    if (valuelen == 0) {
+      ent->nv.value = NULL;
+    } else {
+      ent->nv.value = nghttp2_memdup(value, valuelen, mem);
+      if (ent->nv.value == NULL) {
+        rv = NGHTTP2_ERR_NOMEM;
+        goto fail2;
+      }
+    }
+  } else {
+    ent->nv.value = value;
+  }
+  ent->nv.namelen = namelen;
+  ent->nv.valuelen = valuelen;
+  ent->ref = 1;
+  ent->flags = flags;
+
+  ent->name_hash = name_hash;
+  ent->value_hash = value_hash;
+
+  return 0;
+
+fail2:
+  if (flags & NGHTTP2_HD_FLAG_NAME_ALLOC) {
+    nghttp2_mem_free(mem, ent->nv.name);
+  }
+fail:
+  return rv;
+}
+
+void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem) {
+  assert(ent->ref == 0);
+  if (ent->flags & NGHTTP2_HD_FLAG_NAME_ALLOC) {
+    nghttp2_mem_free(mem, ent->nv.name);
+  }
+  if (ent->flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) {
+    nghttp2_mem_free(mem, ent->nv.value);
+  }
+}
+
+static int hd_ringbuf_init(nghttp2_hd_ringbuf *ringbuf, size_t bufsize,
+                           nghttp2_mem *mem) {
+  size_t size;
+  for (size = 1; size < bufsize; size <<= 1)
+    ;
+  ringbuf->buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size);
+  if (ringbuf->buffer == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+  ringbuf->mask = size - 1;
+  ringbuf->first = 0;
+  ringbuf->len = 0;
+  return 0;
+}
+
+static nghttp2_hd_entry *hd_ringbuf_get(nghttp2_hd_ringbuf *ringbuf,
+                                        size_t idx) {
+  assert(idx < ringbuf->len);
+  return ringbuf->buffer[(ringbuf->first + idx) & ringbuf->mask];
+}
+
+static int hd_ringbuf_reserve(nghttp2_hd_ringbuf *ringbuf, size_t bufsize,
+                              nghttp2_mem *mem) {
+  size_t i;
+  size_t size;
+  nghttp2_hd_entry **buffer;
+
+  if (ringbuf->mask + 1 >= bufsize) {
+    return 0;
+  }
+  for (size = 1; size < bufsize; size <<= 1)
+    ;
+  buffer = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry *) * size);
+  if (buffer == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+  for (i = 0; i < ringbuf->len; ++i) {
+    buffer[i] = hd_ringbuf_get(ringbuf, i);
+  }
+  nghttp2_mem_free(mem, ringbuf->buffer);
+  ringbuf->buffer = buffer;
+  ringbuf->mask = size - 1;
+  ringbuf->first = 0;
+  return 0;
+}
+
+static void hd_ringbuf_free(nghttp2_hd_ringbuf *ringbuf, nghttp2_mem *mem) {
+  size_t i;
+  if (ringbuf == NULL) {
+    return;
+  }
+  for (i = 0; i < ringbuf->len; ++i) {
+    nghttp2_hd_entry *ent = hd_ringbuf_get(ringbuf, i);
+    --ent->ref;
+    nghttp2_hd_entry_free(ent, mem);
+    nghttp2_mem_free(mem, ent);
+  }
+  nghttp2_mem_free(mem, ringbuf->buffer);
+}
+
+static int hd_ringbuf_push_front(nghttp2_hd_ringbuf *ringbuf,
+                                 nghttp2_hd_entry *ent, nghttp2_mem *mem) {
+  int rv;
+
+  rv = hd_ringbuf_reserve(ringbuf, ringbuf->len + 1, mem);
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  ringbuf->buffer[--ringbuf->first & ringbuf->mask] = ent;
+  ++ringbuf->len;
+
+  return 0;
+}
+
+static void hd_ringbuf_pop_back(nghttp2_hd_ringbuf *ringbuf) {
+  assert(ringbuf->len > 0);
+  --ringbuf->len;
+}
+
+static int hd_context_init(nghttp2_hd_context *context, nghttp2_mem *mem) {
+  int rv;
+  context->mem = mem;
+  context->bad = 0;
+  context->hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
+  rv = hd_ringbuf_init(&context->hd_table, context->hd_table_bufsize_max /
+                                               NGHTTP2_HD_ENTRY_OVERHEAD,
+                       mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  context->hd_table_bufsize = 0;
+  return 0;
+}
+
+static void hd_context_free(nghttp2_hd_context *context) {
+  hd_ringbuf_free(&context->hd_table, context->mem);
+}
+
+int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem) {
+  return nghttp2_hd_deflate_init2(
+      deflater, NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE, mem);
+}
+
+int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
+                             size_t deflate_hd_table_bufsize_max,
+                             nghttp2_mem *mem) {
+  int rv;
+  rv = hd_context_init(&deflater->ctx, mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (deflate_hd_table_bufsize_max < NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE) {
+    deflater->notify_table_size_change = 1;
+    deflater->ctx.hd_table_bufsize_max = deflate_hd_table_bufsize_max;
+  } else {
+    deflater->notify_table_size_change = 0;
+  }
+
+  deflater->deflate_hd_table_bufsize_max = deflate_hd_table_bufsize_max;
+  deflater->min_hd_table_bufsize_max = UINT32_MAX;
+
+  return 0;
+}
+
+int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem) {
+  int rv;
+
+  rv = hd_context_init(&inflater->ctx, mem);
+  if (rv != 0) {
+    goto fail;
+  }
+
+  inflater->settings_hd_table_bufsize_max = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
+
+  inflater->ent_keep = NULL;
+  inflater->nv_keep = NULL;
+
+  inflater->opcode = NGHTTP2_HD_OPCODE_NONE;
+  inflater->state = NGHTTP2_HD_STATE_OPCODE;
+
+  rv = nghttp2_bufs_init3(&inflater->nvbufs, NGHTTP2_HD_MAX_NV / 8, 8, 1, 0,
+                          mem);
+
+  if (rv != 0) {
+    goto nvbufs_fail;
+  }
+
+  inflater->huffman_encoded = 0;
+  inflater->index = 0;
+  inflater->left = 0;
+  inflater->shift = 0;
+  inflater->newnamelen = 0;
+  inflater->index_required = 0;
+  inflater->no_index = 0;
+
+  return 0;
+
+nvbufs_fail:
+  hd_context_free(&inflater->ctx);
+fail:
+  return rv;
+}
+
+static void hd_inflate_keep_free(nghttp2_hd_inflater *inflater) {
+  nghttp2_mem *mem;
+
+  mem = inflater->ctx.mem;
+  if (inflater->ent_keep) {
+    if (inflater->ent_keep->ref == 0) {
+      nghttp2_hd_entry_free(inflater->ent_keep, mem);
+      nghttp2_mem_free(mem, inflater->ent_keep);
+    }
+    inflater->ent_keep = NULL;
+  }
+
+  nghttp2_mem_free(mem, inflater->nv_keep);
+  inflater->nv_keep = NULL;
+}
+
+void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater) {
+  hd_context_free(&deflater->ctx);
+}
+
+void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater) {
+  hd_inflate_keep_free(inflater);
+  nghttp2_bufs_free(&inflater->nvbufs);
+  hd_context_free(&inflater->ctx);
+}
+
+static size_t entry_room(size_t namelen, size_t valuelen) {
+  return NGHTTP2_HD_ENTRY_OVERHEAD + namelen + valuelen;
+}
+
+static int emit_indexed_header(nghttp2_nv *nv_out, nghttp2_hd_entry *ent) {
+  DEBUGF(fprintf(stderr, "inflatehd: header emission: "));
+  DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
+  DEBUGF(fprintf(stderr, ": "));
+  DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
+  DEBUGF(fprintf(stderr, "\n"));
+  /* ent->ref may be 0. This happens if the encoder emits literal
+     block larger than header table capacity with indexing. */
+  *nv_out = ent->nv;
+  return 0;
+}
+
+static int emit_literal_header(nghttp2_nv *nv_out, nghttp2_nv *nv) {
+  DEBUGF(fprintf(stderr, "inflatehd: header emission: "));
+  DEBUGF(fwrite(nv->name, nv->namelen, 1, stderr));
+  DEBUGF(fprintf(stderr, ": "));
+  DEBUGF(fwrite(nv->value, nv->valuelen, 1, stderr));
+  DEBUGF(fprintf(stderr, "\n"));
+  *nv_out = *nv;
+  return 0;
+}
+
+static size_t count_encoded_length(size_t n, size_t prefix) {
+  size_t k = (1 << prefix) - 1;
+  size_t len = 0;
+
+  if (n < k) {
+    return 1;
+  }
+
+  n -= k;
+  ++len;
+
+  for (; n >= 128; n >>= 7, ++len)
+    ;
+
+  return len + 1;
+}
+
+static size_t encode_length(uint8_t *buf, size_t n, size_t prefix) {
+  size_t k = (1 << prefix) - 1;
+  uint8_t *begin = buf;
+
+  *buf &= ~k;
+
+  if (n < k) {
+    *buf |= n;
+    return 1;
+  }
+
+  *buf++ |= k;
+  n -= k;
+
+  for (; n >= 128; n >>= 7) {
+    *buf++ = (1 << 7) | (n & 0x7f);
+  }
+
+  *buf++ = (uint8_t)n;
+
+  return (size_t)(buf - begin);
+}
+
+/*
+ * Decodes |prefix| prefixed integer stored from |in|.  The |last|
+ * represents the 1 beyond the last of the valid contiguous memory
+ * region from |in|.  The decoded integer must be less than or equal
+ * to UINT32_MAX.
+ *
+ * If the |initial| is nonzero, it is used as a initial value, this
+ * function assumes the |in| starts with intermediate data.
+ *
+ * An entire integer is decoded successfully, decoded, the |*final| is
+ * set to nonzero.
+ *
+ * This function stores the decoded integer in |*res| if it succeed,
+ * including partial decoding (in this case, number of shift to make
+ * in the next call will be stored in |*shift_ptr|) and returns number
+ * of bytes processed, or returns -1, indicating decoding error.
+ */
+static ssize_t decode_length(uint32_t *res, size_t *shift_ptr, int *final,
+                             uint32_t initial, size_t shift, uint8_t *in,
+                             uint8_t *last, size_t prefix) {
+  uint32_t k = (1 << prefix) - 1;
+  uint32_t n = initial;
+  uint8_t *start = in;
+
+  *shift_ptr = 0;
+  *final = 0;
+
+  if (n == 0) {
+    if ((*in & k) != k) {
+      *res = (*in) & k;
+      *final = 1;
+      return 1;
+    }
+
+    n = k;
+
+    if (++in == last) {
+      *res = n;
+      return (ssize_t)(in - start);
+    }
+  }
+
+  for (; in != last; ++in, shift += 7) {
+    uint32_t add = *in & 0x7f;
+
+    if ((UINT32_MAX >> shift) < add) {
+      DEBUGF(fprintf(stderr, "inflate: integer overflow on shift\n"));
+      return -1;
+    }
+
+    add <<= shift;
+
+    if (UINT32_MAX - add < n) {
+      DEBUGF(fprintf(stderr, "inflate: integer overflow on addition\n"));
+      return -1;
+    }
+
+    n += add;
+
+    if ((*in & (1 << 7)) == 0) {
+      break;
+    }
+  }
+
+  *shift_ptr = shift;
+
+  if (in == last) {
+    *res = n;
+    return (ssize_t)(in - start);
+  }
+
+  *res = n;
+  *final = 1;
+  return (ssize_t)(in + 1 - start);
+}
+
+static int emit_table_size(nghttp2_bufs *bufs, size_t table_size) {
+  int rv;
+  uint8_t *bufp;
+  size_t blocklen;
+  uint8_t sb[16];
+
+  DEBUGF(fprintf(stderr, "deflatehd: emit table_size=%zu\n", table_size));
+
+  blocklen = count_encoded_length(table_size, 5);
+
+  if (sizeof(sb) < blocklen) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  bufp = sb;
+
+  *bufp = 0x20u;
+
+  encode_length(bufp, table_size, 5);
+
+  rv = nghttp2_bufs_add(bufs, sb, blocklen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+static int emit_indexed_block(nghttp2_bufs *bufs, size_t idx) {
+  int rv;
+  size_t blocklen;
+  uint8_t sb[16];
+  uint8_t *bufp;
+
+  blocklen = count_encoded_length(idx + 1, 7);
+
+  DEBUGF(fprintf(stderr, "deflatehd: emit indexed index=%zu, %zu bytes\n", idx,
+                 blocklen));
+
+  if (sizeof(sb) < blocklen) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  bufp = sb;
+  *bufp = 0x80u;
+  encode_length(bufp, idx + 1, 7);
+
+  rv = nghttp2_bufs_add(bufs, sb, blocklen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+static int emit_string(nghttp2_bufs *bufs, const uint8_t *str, size_t len) {
+  int rv;
+  uint8_t sb[16];
+  uint8_t *bufp;
+  size_t blocklen;
+  size_t enclen;
+  int huffman = 0;
+
+  enclen = nghttp2_hd_huff_encode_count(str, len);
+
+  if (enclen < len) {
+    huffman = 1;
+  } else {
+    enclen = len;
+  }
+
+  blocklen = count_encoded_length(enclen, 7);
+
+  DEBUGF(fprintf(stderr, "deflatehd: emit string str="));
+  DEBUGF(fwrite(str, len, 1, stderr));
+  DEBUGF(fprintf(stderr, ", length=%zu, huffman=%d, encoded_length=%zu\n", len,
+                 huffman, enclen));
+
+  if (sizeof(sb) < blocklen) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  bufp = sb;
+  *bufp = huffman ? 1 << 7 : 0;
+  encode_length(bufp, enclen, 7);
+
+  rv = nghttp2_bufs_add(bufs, sb, blocklen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (huffman) {
+    rv = nghttp2_hd_huff_encode(bufs, str, len);
+  } else {
+    assert(enclen == len);
+    rv = nghttp2_bufs_add(bufs, str, len);
+  }
+
+  return rv;
+}
+
+static uint8_t pack_first_byte(int inc_indexing, int no_index) {
+  if (inc_indexing) {
+    return 0x40u;
+  }
+
+  if (no_index) {
+    return 0x10u;
+  }
+
+  return 0;
+}
+
+static int emit_indname_block(nghttp2_bufs *bufs, size_t idx,
+                              const nghttp2_nv *nv, int inc_indexing) {
+  int rv;
+  uint8_t *bufp;
+  size_t blocklen;
+  uint8_t sb[16];
+  size_t prefixlen;
+  int no_index;
+
+  no_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0;
+
+  if (inc_indexing) {
+    prefixlen = 6;
+  } else {
+    prefixlen = 4;
+  }
+
+  DEBUGF(fprintf(stderr, "deflatehd: emit indname index=%zu, valuelen=%zu, "
+                         "indexing=%d, no_index=%d\n",
+                 idx, nv->valuelen, inc_indexing, no_index));
+
+  blocklen = count_encoded_length(idx + 1, prefixlen);
+
+  if (sizeof(sb) < blocklen) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  bufp = sb;
+
+  *bufp = pack_first_byte(inc_indexing, no_index);
+
+  encode_length(bufp, idx + 1, prefixlen);
+
+  rv = nghttp2_bufs_add(bufs, sb, blocklen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = emit_string(bufs, nv->value, nv->valuelen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+static int emit_newname_block(nghttp2_bufs *bufs, const nghttp2_nv *nv,
+                              int inc_indexing) {
+  int rv;
+  int no_index;
+
+  no_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0;
+
+  DEBUGF(fprintf(stderr, "deflatehd: emit newname namelen=%zu, valuelen=%zu, "
+                         "indexing=%d, no_index=%d\n",
+                 nv->namelen, nv->valuelen, inc_indexing, no_index));
+
+  rv = nghttp2_bufs_addb(bufs, pack_first_byte(inc_indexing, no_index));
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = emit_string(bufs, nv->name, nv->namelen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = emit_string(bufs, nv->value, nv->valuelen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+static nghttp2_hd_entry *add_hd_table_incremental(nghttp2_hd_context *context,
+                                                  const nghttp2_nv *nv,
+                                                  uint32_t name_hash,
+                                                  uint32_t value_hash,
+                                                  uint8_t entry_flags) {
+  int rv;
+  nghttp2_hd_entry *new_ent;
+  size_t room;
+  nghttp2_mem *mem;
+
+  mem = context->mem;
+  room = entry_room(nv->namelen, nv->valuelen);
+
+  while (context->hd_table_bufsize + room > context->hd_table_bufsize_max &&
+         context->hd_table.len > 0) {
+
+    size_t idx = context->hd_table.len - 1;
+    nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx);
+
+    context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen);
+
+    DEBUGF(fprintf(stderr, "hpack: remove item from header table: "));
+    DEBUGF(fwrite(ent->nv.name, ent->nv.namelen, 1, stderr));
+    DEBUGF(fprintf(stderr, ": "));
+    DEBUGF(fwrite(ent->nv.value, ent->nv.valuelen, 1, stderr));
+    DEBUGF(fprintf(stderr, "\n"));
+    hd_ringbuf_pop_back(&context->hd_table);
+    if (--ent->ref == 0) {
+      nghttp2_hd_entry_free(ent, mem);
+      nghttp2_mem_free(mem, ent);
+    }
+  }
+
+  new_ent = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_entry));
+  if (new_ent == NULL) {
+    return NULL;
+  }
+
+  rv = nghttp2_hd_entry_init(new_ent, entry_flags, nv->name, nv->namelen,
+                             nv->value, nv->valuelen, name_hash, value_hash,
+                             mem);
+  if (rv != 0) {
+    nghttp2_mem_free(mem, new_ent);
+    return NULL;
+  }
+
+  if (room > context->hd_table_bufsize_max) {
+    /* The entry taking more than NGHTTP2_HD_MAX_BUFFER_SIZE is
+       immediately evicted. */
+    --new_ent->ref;
+  } else {
+    rv = hd_ringbuf_push_front(&context->hd_table, new_ent, mem);
+
+    if (rv != 0) {
+      --new_ent->ref;
+
+      if ((entry_flags & NGHTTP2_HD_FLAG_NAME_ALLOC) &&
+          (entry_flags & NGHTTP2_HD_FLAG_NAME_GIFT)) {
+        /* nv->name are managed by caller. */
+        new_ent->nv.name = NULL;
+        new_ent->nv.namelen = 0;
+      }
+      if ((entry_flags & NGHTTP2_HD_FLAG_VALUE_ALLOC) &&
+          (entry_flags & NGHTTP2_HD_FLAG_VALUE_GIFT)) {
+        /* nv->value are managed by caller. */
+        new_ent->nv.value = NULL;
+        new_ent->nv.valuelen = 0;
+      }
+
+      nghttp2_hd_entry_free(new_ent, mem);
+      nghttp2_mem_free(mem, new_ent);
+
+      return NULL;
+    }
+
+    context->hd_table_bufsize += room;
+  }
+  return new_ent;
+}
+
+static int name_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
+  return a->namelen == b->namelen && memeq(a->name, b->name, a->namelen);
+}
+
+static int value_eq(const nghttp2_nv *a, const nghttp2_nv *b) {
+  return a->valuelen == b->valuelen && memeq(a->value, b->value, a->valuelen);
+}
+
+typedef struct {
+  ssize_t index;
+  /* Nonzero if both name and value are matched. */
+  uint8_t name_value_match;
+} search_result;
+
+static search_result search_hd_table(nghttp2_hd_context *context,
+                                     const nghttp2_nv *nv, uint32_t name_hash,
+                                     uint32_t value_hash) {
+  ssize_t left = -1, right = (ssize_t)STATIC_TABLE_LENGTH;
+  search_result res = {-1, 0};
+  size_t i;
+  int use_index = (nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) == 0;
+
+  /* Search dynamic table first, so that we can find recently used
+     entry first */
+  if (use_index) {
+    for (i = 0; i < context->hd_table.len; ++i) {
+      nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, i);
+      if (ent->name_hash != name_hash || !name_eq(&ent->nv, nv)) {
+        continue;
+      }
+
+      if (res.index == -1) {
+        res.index = (ssize_t)(i + NGHTTP2_STATIC_TABLE_LENGTH);
+      }
+
+      if (ent->value_hash == value_hash && value_eq(&ent->nv, nv)) {
+        res.index = (ssize_t)(i + NGHTTP2_STATIC_TABLE_LENGTH);
+        res.name_value_match = 1;
+        return res;
+      }
+    }
+  }
+
+  while (right - left > 1) {
+    ssize_t mid = (left + right) / 2;
+    nghttp2_hd_entry *ent = &static_table[mid].ent;
+    if (ent->name_hash < name_hash) {
+      left = mid;
+    } else {
+      right = mid;
+    }
+  }
+
+  for (i = right; i < STATIC_TABLE_LENGTH; ++i) {
+    nghttp2_hd_entry *ent = &static_table[i].ent;
+    if (ent->name_hash != name_hash) {
+      break;
+    }
+
+    if (name_eq(&ent->nv, nv)) {
+      if (res.index == -1) {
+        res.index = (ssize_t)(static_table[i].index);
+      }
+      if (use_index && ent->value_hash == value_hash &&
+          value_eq(&ent->nv, nv)) {
+        res.index = (ssize_t)(static_table[i].index);
+        res.name_value_match = 1;
+        return res;
+      }
+    }
+  }
+
+  return res;
+}
+
+static void hd_context_shrink_table_size(nghttp2_hd_context *context) {
+  nghttp2_mem *mem;
+
+  mem = context->mem;
+
+  while (context->hd_table_bufsize > context->hd_table_bufsize_max &&
+         context->hd_table.len > 0) {
+    size_t idx = context->hd_table.len - 1;
+    nghttp2_hd_entry *ent = hd_ringbuf_get(&context->hd_table, idx);
+    context->hd_table_bufsize -= entry_room(ent->nv.namelen, ent->nv.valuelen);
+    hd_ringbuf_pop_back(&context->hd_table);
+    if (--ent->ref == 0) {
+      nghttp2_hd_entry_free(ent, mem);
+      nghttp2_mem_free(mem, ent);
+    }
+  }
+}
+
+int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
+                                         size_t settings_hd_table_bufsize_max) {
+  size_t next_bufsize = nghttp2_min(settings_hd_table_bufsize_max,
+                                    deflater->deflate_hd_table_bufsize_max);
+
+  deflater->ctx.hd_table_bufsize_max = next_bufsize;
+
+  deflater->min_hd_table_bufsize_max =
+      nghttp2_min(deflater->min_hd_table_bufsize_max, next_bufsize);
+
+  deflater->notify_table_size_change = 1;
+
+  hd_context_shrink_table_size(&deflater->ctx);
+  return 0;
+}
+
+int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
+                                         size_t settings_hd_table_bufsize_max) {
+  inflater->settings_hd_table_bufsize_max = settings_hd_table_bufsize_max;
+  inflater->ctx.hd_table_bufsize_max = settings_hd_table_bufsize_max;
+  hd_context_shrink_table_size(&inflater->ctx);
+  return 0;
+}
+
+#define INDEX_RANGE_VALID(context, idx)                                        \
+  ((idx) < (context)->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH)
+
+static size_t get_max_index(nghttp2_hd_context *context) {
+  return context->hd_table.len + NGHTTP2_STATIC_TABLE_LENGTH - 1;
+}
+
+nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context,
+                                       size_t idx) {
+  assert(INDEX_RANGE_VALID(context, idx));
+  if (idx >= NGHTTP2_STATIC_TABLE_LENGTH) {
+    return hd_ringbuf_get(&context->hd_table,
+                          idx - NGHTTP2_STATIC_TABLE_LENGTH);
+  } else {
+    return &static_table[static_table_index[idx]].ent;
+  }
+}
+
+#define name_match(NV, NAME)                                                   \
+  (nv->namelen == sizeof(NAME) - 1 && memeq(nv->name, NAME, sizeof(NAME) - 1))
+
+static int hd_deflate_should_indexing(nghttp2_hd_deflater *deflater,
+                                      const nghttp2_nv *nv) {
+  if ((nv->flags & NGHTTP2_NV_FLAG_NO_INDEX) ||
+      entry_room(nv->namelen, nv->valuelen) >
+          deflater->ctx.hd_table_bufsize_max * 3 / 4) {
+    return 0;
+  }
+#ifdef NGHTTP2_XHD
+  return !name_match(nv, NGHTTP2_XHD);
+#else  /* !NGHTTP2_XHD */
+  return !name_match(nv, ":path") && !name_match(nv, "content-length") &&
+         !name_match(nv, "set-cookie") && !name_match(nv, "etag") &&
+         !name_match(nv, "if-modified-since") &&
+         !name_match(nv, "if-none-match") && !name_match(nv, "location") &&
+         !name_match(nv, "age");
+#endif /* !NGHTTP2_XHD */
+}
+
+static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
+                      const nghttp2_nv *nv) {
+  int rv;
+  search_result res;
+  ssize_t idx;
+  int incidx = 0;
+  uint32_t name_hash = hash(nv->name, nv->namelen);
+  uint32_t value_hash = hash(nv->value, nv->valuelen);
+  nghttp2_mem *mem;
+
+  DEBUGF(fprintf(stderr, "deflatehd: deflating "));
+  DEBUGF(fwrite(nv->name, nv->namelen, 1, stderr));
+  DEBUGF(fprintf(stderr, ": "));
+  DEBUGF(fwrite(nv->value, nv->valuelen, 1, stderr));
+  DEBUGF(fprintf(stderr, "\n"));
+
+  mem = deflater->ctx.mem;
+
+  res = search_hd_table(&deflater->ctx, nv, name_hash, value_hash);
+
+  idx = res.index;
+
+  if (res.name_value_match) {
+
+    DEBUGF(fprintf(stderr, "deflatehd: name/value match index=%zd\n", idx));
+
+    rv = emit_indexed_block(bufs, idx);
+    if (rv != 0) {
+      return rv;
+    }
+
+    return 0;
+  }
+
+  if (res.index != -1) {
+    DEBUGF(fprintf(stderr, "deflatehd: name match index=%zd\n", res.index));
+  }
+
+  if (hd_deflate_should_indexing(deflater, nv)) {
+    nghttp2_hd_entry *new_ent;
+    if (idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) {
+      nghttp2_nv nv_indname;
+      nv_indname = *nv;
+      nv_indname.name = nghttp2_hd_table_get(&deflater->ctx, idx)->nv.name;
+      new_ent =
+          add_hd_table_incremental(&deflater->ctx, &nv_indname, name_hash,
+                                   value_hash, NGHTTP2_HD_FLAG_VALUE_ALLOC);
+    } else {
+      new_ent = add_hd_table_incremental(
+          &deflater->ctx, nv, name_hash, value_hash,
+          NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_VALUE_ALLOC);
+    }
+    if (!new_ent) {
+      return NGHTTP2_ERR_HEADER_COMP;
+    }
+    if (new_ent->ref == 0) {
+      nghttp2_hd_entry_free(new_ent, mem);
+      nghttp2_mem_free(mem, new_ent);
+    }
+    incidx = 1;
+  }
+  if (idx == -1) {
+    rv = emit_newname_block(bufs, nv, incidx);
+  } else {
+    rv = emit_indname_block(bufs, idx, nv, incidx);
+  }
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
+                               nghttp2_bufs *bufs, const nghttp2_nv *nv,
+                               size_t nvlen) {
+  size_t i;
+  int rv = 0;
+
+  if (deflater->ctx.bad) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  if (deflater->notify_table_size_change) {
+    size_t min_hd_table_bufsize_max;
+
+    min_hd_table_bufsize_max = deflater->min_hd_table_bufsize_max;
+
+    deflater->notify_table_size_change = 0;
+    deflater->min_hd_table_bufsize_max = UINT32_MAX;
+
+    if (deflater->ctx.hd_table_bufsize_max > min_hd_table_bufsize_max) {
+
+      rv = emit_table_size(bufs, min_hd_table_bufsize_max);
+
+      if (rv != 0) {
+        goto fail;
+      }
+    }
+
+    rv = emit_table_size(bufs, deflater->ctx.hd_table_bufsize_max);
+
+    if (rv != 0) {
+      goto fail;
+    }
+  }
+
+  for (i = 0; i < nvlen; ++i) {
+    rv = deflate_nv(deflater, bufs, &nv[i]);
+    if (rv != 0) {
+      goto fail;
+    }
+  }
+
+  DEBUGF(
+      fprintf(stderr, "deflatehd: all input name/value pairs were deflated\n"));
+
+  return 0;
+fail:
+  DEBUGF(fprintf(stderr, "deflatehd: error return %d\n", rv));
+
+  deflater->ctx.bad = 1;
+  return rv;
+}
+
+ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf,
+                              size_t buflen, const nghttp2_nv *nv,
+                              size_t nvlen) {
+  nghttp2_bufs bufs;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = deflater->ctx.mem;
+
+  rv = nghttp2_bufs_wrap_init(&bufs, buf, buflen, mem);
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nv, nvlen);
+
+  buflen = nghttp2_bufs_len(&bufs);
+
+  nghttp2_bufs_wrap_free(&bufs);
+
+  if (rv == NGHTTP2_ERR_BUFFER_ERROR) {
+    return NGHTTP2_ERR_INSUFF_BUFSIZE;
+  }
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  return (ssize_t)buflen;
+}
+
+size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater _U_,
+                                const nghttp2_nv *nva, size_t nvlen) {
+  size_t n = 0;
+  size_t i;
+
+  /* Possible Maximum Header Table Size Change.  Encoding (1u << 31) -
+     1 using 4 bit prefix requires 6 bytes.  We may emit this at most
+     twice. */
+  n += 12;
+
+  /* Use Literal Header Field without indexing - New Name, since it is
+     most space consuming format.  Also we choose the less one between
+     non-huffman and huffman, so using literal byte count is
+     sufficient for upper bound.
+
+     Encoding (1u << 31) - 1 using 7 bit prefix requires 6 bytes.  We
+     need 2 of this for |nvlen| header fields. */
+  n += 6 * 2 * nvlen;
+
+  for (i = 0; i < nvlen; ++i) {
+    n += nva[i].namelen + nva[i].valuelen;
+  }
+
+  return n;
+}
+
+int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
+                           size_t deflate_hd_table_bufsize_max) {
+  return nghttp2_hd_deflate_new2(deflater_ptr, deflate_hd_table_bufsize_max,
+                                 NULL);
+}
+
+int nghttp2_hd_deflate_new2(nghttp2_hd_deflater **deflater_ptr,
+                            size_t deflate_hd_table_bufsize_max,
+                            nghttp2_mem *mem) {
+  int rv;
+  nghttp2_hd_deflater *deflater;
+
+  if (mem == NULL) {
+    mem = nghttp2_mem_default();
+  }
+
+  deflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_deflater));
+
+  if (deflater == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  rv = nghttp2_hd_deflate_init2(deflater, deflate_hd_table_bufsize_max, mem);
+
+  if (rv != 0) {
+    nghttp2_mem_free(mem, deflater);
+
+    return rv;
+  }
+
+  *deflater_ptr = deflater;
+
+  return 0;
+}
+
+void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater) {
+  nghttp2_mem *mem;
+
+  mem = deflater->ctx.mem;
+
+  nghttp2_hd_deflate_free(deflater);
+
+  nghttp2_mem_free(mem, deflater);
+}
+
+static void hd_inflate_set_huffman_encoded(nghttp2_hd_inflater *inflater,
+                                           const uint8_t *in) {
+  inflater->huffman_encoded = (*in & (1 << 7)) != 0;
+}
+
+/*
+ * Decodes the integer from the range [in, last).  The result is
+ * assigned to |inflater->left|.  If the |inflater->left| is 0, then
+ * it performs variable integer decoding from scratch. Otherwise, it
+ * uses the |inflater->left| as the initial value and continues to
+ * decode assuming that [in, last) begins with intermediary sequence.
+ *
+ * This function returns the number of bytes read if it succeeds, or
+ * one of the following negative error codes:
+ *
+ * NGHTTP2_ERR_HEADER_COMP
+ *   Integer decoding failed
+ */
+static ssize_t hd_inflate_read_len(nghttp2_hd_inflater *inflater, int *rfin,
+                                   uint8_t *in, uint8_t *last, size_t prefix,
+                                   size_t maxlen) {
+  ssize_t rv;
+  uint32_t out;
+
+  *rfin = 0;
+
+  rv = decode_length(&out, &inflater->shift, rfin, (uint32_t)inflater->left,
+                     inflater->shift, in, last, prefix);
+
+  if (rv == -1) {
+    DEBUGF(fprintf(stderr, "inflatehd: integer decoding failed\n"));
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  if (out > maxlen) {
+    DEBUGF(fprintf(
+        stderr, "inflatehd: integer exceeded the maximum value %zu\n", maxlen));
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  inflater->left = out;
+
+  DEBUGF(fprintf(stderr, "inflatehd: decoded integer is %u\n", out));
+
+  return rv;
+}
+
+/*
+ * Reads |inflater->left| bytes from the range [in, last) and performs
+ * huffman decoding against them and pushes the result into the
+ * |buffer|.
+ *
+ * This function returns the number of bytes read if it succeeds, or
+ * one of the following negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ * NGHTTP2_ERR_HEADER_COMP
+ *   Huffman decoding failed
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+static ssize_t hd_inflate_read_huff(nghttp2_hd_inflater *inflater,
+                                    nghttp2_bufs *bufs, uint8_t *in,
+                                    uint8_t *last) {
+  ssize_t readlen;
+  int final = 0;
+  if ((size_t)(last - in) >= inflater->left) {
+    last = in + inflater->left;
+    final = 1;
+  }
+  readlen = nghttp2_hd_huff_decode(&inflater->huff_decode_ctx, bufs, in,
+                                   last - in, final);
+
+  if (readlen < 0) {
+    DEBUGF(fprintf(stderr, "inflatehd: huffman decoding failed\n"));
+    return readlen;
+  }
+  inflater->left -= (size_t)readlen;
+  return readlen;
+}
+
+/*
+ * Reads |inflater->left| bytes from the range [in, last) and copies
+ * them into the |buffer|.
+ *
+ * This function returns the number of bytes read if it succeeds, or
+ * one of the following negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ * NGHTTP2_ERR_HEADER_COMP
+ *   Header decompression failed
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+static ssize_t hd_inflate_read(nghttp2_hd_inflater *inflater,
+                               nghttp2_bufs *bufs, uint8_t *in, uint8_t *last) {
+  int rv;
+  size_t len = nghttp2_min((size_t)(last - in), inflater->left);
+  rv = nghttp2_bufs_add(bufs, in, len);
+  if (rv != 0) {
+    return rv;
+  }
+  inflater->left -= len;
+  return (ssize_t)len;
+}
+
+/*
+ * Finalize indexed header representation reception. If header is
+ * emitted, |*nv_out| is filled with that value and 0 is returned. If
+ * no header is emitted, 1 is returned.
+ *
+ * This function returns either 0 or 1 if it succeeds, or one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ */
+static int hd_inflate_commit_indexed(nghttp2_hd_inflater *inflater,
+                                     nghttp2_nv *nv_out) {
+  nghttp2_hd_entry *ent = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
+
+  emit_indexed_header(nv_out, ent);
+
+  return 0;
+}
+
+static int hd_inflate_remove_bufs(nghttp2_hd_inflater *inflater, nghttp2_nv *nv,
+                                  int value_only) {
+  ssize_t rv;
+  size_t buflen;
+  uint8_t *buf;
+  nghttp2_buf *pbuf;
+
+  if (inflater->index_required ||
+      inflater->nvbufs.head != inflater->nvbufs.cur) {
+
+    rv = nghttp2_bufs_remove(&inflater->nvbufs, &buf);
+
+    if (rv < 0) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+
+    buflen = rv;
+
+    if (value_only) {
+      nv->name = NULL;
+      nv->namelen = 0;
+    } else {
+      nv->name = buf;
+      nv->namelen = inflater->newnamelen;
+    }
+
+    nv->value = buf + nv->namelen;
+    nv->valuelen = buflen - nv->namelen;
+
+    return 0;
+  }
+
+  /* If we are not going to store header in header table and
+     name/value are in first chunk, we just refer them from nv,
+     instead of mallocing another memory. */
+
+  pbuf = &inflater->nvbufs.head->buf;
+
+  if (value_only) {
+    nv->name = NULL;
+    nv->namelen = 0;
+  } else {
+    nv->name = pbuf->pos;
+    nv->namelen = inflater->newnamelen;
+  }
+
+  nv->value = pbuf->pos + nv->namelen;
+  nv->valuelen = nghttp2_buf_len(pbuf) - nv->namelen;
+
+  /* Resetting does not change the content of first buffer */
+  nghttp2_bufs_reset(&inflater->nvbufs);
+
+  return 0;
+}
+
+/*
+ * Finalize literal header representation - new name- reception. If
+ * header is emitted, |*nv_out| is filled with that value and 0 is
+ * returned.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ */
+static int hd_inflate_commit_newname(nghttp2_hd_inflater *inflater,
+                                     nghttp2_nv *nv_out) {
+  int rv;
+  nghttp2_nv nv;
+  nghttp2_mem *mem;
+
+  mem = inflater->ctx.mem;
+
+  rv = hd_inflate_remove_bufs(inflater, &nv, 0 /* name and value */);
+  if (rv != 0) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  if (inflater->no_index) {
+    nv.flags = NGHTTP2_NV_FLAG_NO_INDEX;
+  } else {
+    nv.flags = NGHTTP2_NV_FLAG_NONE;
+  }
+
+  if (inflater->index_required) {
+    nghttp2_hd_entry *new_ent;
+    uint8_t ent_flags;
+
+    /* nv->value points to the middle of the buffer pointed by
+       nv->name.  So we just need to keep track of nv->name for memory
+       management. */
+    ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC | NGHTTP2_HD_FLAG_NAME_GIFT;
+
+    new_ent =
+        add_hd_table_incremental(&inflater->ctx, &nv, hash(nv.name, nv.namelen),
+                                 hash(nv.value, nv.valuelen), ent_flags);
+
+    if (new_ent) {
+      emit_indexed_header(nv_out, new_ent);
+      inflater->ent_keep = new_ent;
+
+      return 0;
+    }
+
+    nghttp2_mem_free(mem, nv.name);
+
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  emit_literal_header(nv_out, &nv);
+
+  if (nv.name != inflater->nvbufs.head->buf.pos) {
+    inflater->nv_keep = nv.name;
+  }
+
+  return 0;
+}
+
+/*
+ * Finalize literal header representation - indexed name-
+ * reception. If header is emitted, |*nv_out| is filled with that
+ * value and 0 is returned.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ */
+static int hd_inflate_commit_indname(nghttp2_hd_inflater *inflater,
+                                     nghttp2_nv *nv_out) {
+  int rv;
+  nghttp2_nv nv;
+  nghttp2_hd_entry *ent_name;
+  nghttp2_mem *mem;
+
+  mem = inflater->ctx.mem;
+
+  rv = hd_inflate_remove_bufs(inflater, &nv, 1 /* value only */);
+  if (rv != 0) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  if (inflater->no_index) {
+    nv.flags = NGHTTP2_NV_FLAG_NO_INDEX;
+  } else {
+    nv.flags = NGHTTP2_NV_FLAG_NONE;
+  }
+
+  ent_name = nghttp2_hd_table_get(&inflater->ctx, inflater->index);
+
+  nv.name = ent_name->nv.name;
+  nv.namelen = ent_name->nv.namelen;
+
+  if (inflater->index_required) {
+    nghttp2_hd_entry *new_ent;
+    uint8_t ent_flags;
+    int static_name;
+
+    ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC | NGHTTP2_HD_FLAG_VALUE_GIFT;
+    static_name = inflater->index < NGHTTP2_STATIC_TABLE_LENGTH;
+
+    if (!static_name) {
+      ent_flags |= NGHTTP2_HD_FLAG_NAME_ALLOC;
+      /* For entry in static table, we must not touch ref, because it
+         is shared by threads */
+      ++ent_name->ref;
+    }
+
+    new_ent = add_hd_table_incremental(&inflater->ctx, &nv, ent_name->name_hash,
+                                       hash(nv.value, nv.valuelen), ent_flags);
+
+    if (!static_name && --ent_name->ref == 0) {
+      nghttp2_hd_entry_free(ent_name, mem);
+      nghttp2_mem_free(mem, ent_name);
+    }
+
+    if (new_ent) {
+      emit_indexed_header(nv_out, new_ent);
+
+      inflater->ent_keep = new_ent;
+
+      return 0;
+    }
+
+    nghttp2_mem_free(mem, nv.value);
+
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  emit_literal_header(nv_out, &nv);
+
+  if (nv.value != inflater->nvbufs.head->buf.pos) {
+    inflater->nv_keep = nv.value;
+  }
+
+  return 0;
+}
+
+ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater, nghttp2_nv *nv_out,
+                              int *inflate_flags, uint8_t *in, size_t inlen,
+                              int in_final) {
+  ssize_t rv = 0;
+  uint8_t *first = in;
+  uint8_t *last = in + inlen;
+  int rfin = 0;
+
+  if (inflater->ctx.bad) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+
+  DEBUGF(fprintf(stderr, "inflatehd: start state=%d\n", inflater->state));
+  hd_inflate_keep_free(inflater);
+  *inflate_flags = NGHTTP2_HD_INFLATE_NONE;
+  for (; in != last;) {
+    switch (inflater->state) {
+    case NGHTTP2_HD_STATE_OPCODE:
+      if ((*in & 0xe0u) == 0x20u) {
+        DEBUGF(fprintf(stderr, "inflatehd: header table size change\n"));
+        inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
+        inflater->state = NGHTTP2_HD_STATE_READ_TABLE_SIZE;
+      } else if (*in & 0x80u) {
+        DEBUGF(fprintf(stderr, "inflatehd: indexed repr\n"));
+        inflater->opcode = NGHTTP2_HD_OPCODE_INDEXED;
+        inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
+      } else {
+        if (*in == 0x40u || *in == 0 || *in == 0x10u) {
+          DEBUGF(
+              fprintf(stderr, "inflatehd: literal header repr - new name\n"));
+          inflater->opcode = NGHTTP2_HD_OPCODE_NEWNAME;
+          inflater->state = NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN;
+        } else {
+          DEBUGF(fprintf(stderr,
+                         "inflatehd: literal header repr - indexed name\n"));
+          inflater->opcode = NGHTTP2_HD_OPCODE_INDNAME;
+          inflater->state = NGHTTP2_HD_STATE_READ_INDEX;
+        }
+        inflater->index_required = (*in & 0x40) != 0;
+        inflater->no_index = (*in & 0xf0u) == 0x10u;
+        DEBUGF(fprintf(stderr, "inflatehd: indexing required=%d, no_index=%d\n",
+                       inflater->index_required, inflater->no_index));
+        if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
+          ++in;
+        }
+      }
+      inflater->left = 0;
+      inflater->shift = 0;
+      break;
+    case NGHTTP2_HD_STATE_READ_TABLE_SIZE:
+      rfin = 0;
+      rv = hd_inflate_read_len(inflater, &rfin, in, last, 5,
+                               inflater->settings_hd_table_bufsize_max);
+      if (rv < 0) {
+        goto fail;
+      }
+      in += rv;
+      if (!rfin) {
+        goto almost_ok;
+      }
+      DEBUGF(fprintf(stderr, "inflatehd: table_size=%zu\n", inflater->left));
+      inflater->ctx.hd_table_bufsize_max = inflater->left;
+      hd_context_shrink_table_size(&inflater->ctx);
+      inflater->state = NGHTTP2_HD_STATE_OPCODE;
+      break;
+    case NGHTTP2_HD_STATE_READ_INDEX: {
+      size_t prefixlen;
+
+      if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
+        prefixlen = 7;
+      } else if (inflater->index_required) {
+        prefixlen = 6;
+      } else {
+        prefixlen = 4;
+      }
+
+      rfin = 0;
+      rv = hd_inflate_read_len(inflater, &rfin, in, last, prefixlen,
+                               get_max_index(&inflater->ctx) + 1);
+      if (rv < 0) {
+        goto fail;
+      }
+
+      in += rv;
+
+      if (!rfin) {
+        goto almost_ok;
+      }
+
+      if (inflater->left == 0) {
+        rv = NGHTTP2_ERR_HEADER_COMP;
+        goto fail;
+      }
+
+      DEBUGF(fprintf(stderr, "inflatehd: index=%zu\n", inflater->left));
+      if (inflater->opcode == NGHTTP2_HD_OPCODE_INDEXED) {
+        inflater->index = inflater->left;
+        --inflater->index;
+
+        rv = hd_inflate_commit_indexed(inflater, nv_out);
+        if (rv < 0) {
+          goto fail;
+        }
+        inflater->state = NGHTTP2_HD_STATE_OPCODE;
+        /* If rv == 1, no header was emitted */
+        if (rv == 0) {
+          *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
+          return (ssize_t)(in - first);
+        }
+      } else {
+        inflater->index = inflater->left;
+        --inflater->index;
+
+        inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
+      }
+      break;
+    }
+    case NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN:
+      hd_inflate_set_huffman_encoded(inflater, in);
+      inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN;
+      inflater->left = 0;
+      inflater->shift = 0;
+      DEBUGF(fprintf(stderr, "inflatehd: huffman encoded=%d\n",
+                     inflater->huffman_encoded != 0));
+    /* Fall through */
+    case NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN:
+      rfin = 0;
+      rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV);
+      if (rv < 0) {
+        goto fail;
+      }
+      in += rv;
+      if (!rfin) {
+        DEBUGF(fprintf(stderr,
+                       "inflatehd: integer not fully decoded. current=%zu\n",
+                       inflater->left));
+
+        goto almost_ok;
+      }
+
+      if (inflater->huffman_encoded) {
+        nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx);
+
+        inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF;
+      } else {
+        inflater->state = NGHTTP2_HD_STATE_NEWNAME_READ_NAME;
+      }
+      break;
+    case NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF:
+      rv = hd_inflate_read_huff(inflater, &inflater->nvbufs, in, last);
+      if (rv < 0) {
+        goto fail;
+      }
+
+      in += rv;
+
+      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));
+
+      if (inflater->left) {
+        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
+                       inflater->left));
+
+        goto almost_ok;
+      }
+
+      inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);
+
+      inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
+
+      break;
+    case NGHTTP2_HD_STATE_NEWNAME_READ_NAME:
+      rv = hd_inflate_read(inflater, &inflater->nvbufs, in, last);
+      if (rv < 0) {
+        goto fail;
+      }
+
+      in += rv;
+
+      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));
+      if (inflater->left) {
+        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
+                       inflater->left));
+
+        goto almost_ok;
+      }
+
+      inflater->newnamelen = nghttp2_bufs_len(&inflater->nvbufs);
+
+      inflater->state = NGHTTP2_HD_STATE_CHECK_VALUELEN;
+
+      break;
+    case NGHTTP2_HD_STATE_CHECK_VALUELEN:
+      hd_inflate_set_huffman_encoded(inflater, in);
+      inflater->state = NGHTTP2_HD_STATE_READ_VALUELEN;
+      inflater->left = 0;
+      inflater->shift = 0;
+      DEBUGF(fprintf(stderr, "inflatehd: huffman encoded=%d\n",
+                     inflater->huffman_encoded != 0));
+    /* Fall through */
+    case NGHTTP2_HD_STATE_READ_VALUELEN:
+      rfin = 0;
+      rv = hd_inflate_read_len(inflater, &rfin, in, last, 7, NGHTTP2_HD_MAX_NV);
+      if (rv < 0) {
+        goto fail;
+      }
+
+      in += rv;
+
+      if (!rfin) {
+        goto almost_ok;
+      }
+
+      DEBUGF(fprintf(stderr, "inflatehd: valuelen=%zu\n", inflater->left));
+      if (inflater->left == 0) {
+        if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
+          rv = hd_inflate_commit_newname(inflater, nv_out);
+        } else {
+          rv = hd_inflate_commit_indname(inflater, nv_out);
+        }
+        if (rv != 0) {
+          goto fail;
+        }
+        inflater->state = NGHTTP2_HD_STATE_OPCODE;
+        *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
+        return (ssize_t)(in - first);
+      }
+
+      if (inflater->huffman_encoded) {
+        nghttp2_hd_huff_decode_context_init(&inflater->huff_decode_ctx);
+
+        inflater->state = NGHTTP2_HD_STATE_READ_VALUEHUFF;
+      } else {
+        inflater->state = NGHTTP2_HD_STATE_READ_VALUE;
+      }
+      break;
+    case NGHTTP2_HD_STATE_READ_VALUEHUFF:
+      rv = hd_inflate_read_huff(inflater, &inflater->nvbufs, in, last);
+      if (rv < 0) {
+        goto fail;
+      }
+
+      in += rv;
+
+      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));
+
+      if (inflater->left) {
+        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
+                       inflater->left));
+
+        goto almost_ok;
+      }
+
+      if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
+        rv = hd_inflate_commit_newname(inflater, nv_out);
+      } else {
+        rv = hd_inflate_commit_indname(inflater, nv_out);
+      }
+
+      if (rv != 0) {
+        goto fail;
+      }
+
+      inflater->state = NGHTTP2_HD_STATE_OPCODE;
+      *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
+
+      return (ssize_t)(in - first);
+    case NGHTTP2_HD_STATE_READ_VALUE:
+      rv = hd_inflate_read(inflater, &inflater->nvbufs, in, last);
+      if (rv < 0) {
+        DEBUGF(fprintf(stderr, "inflatehd: value read failure %zd: %s\n", rv,
+                       nghttp2_strerror((int)rv)));
+        goto fail;
+      }
+
+      in += rv;
+
+      DEBUGF(fprintf(stderr, "inflatehd: %zd bytes read\n", rv));
+
+      if (inflater->left) {
+        DEBUGF(fprintf(stderr, "inflatehd: still %zu bytes to go\n",
+                       inflater->left));
+        goto almost_ok;
+      }
+
+      if (inflater->opcode == NGHTTP2_HD_OPCODE_NEWNAME) {
+        rv = hd_inflate_commit_newname(inflater, nv_out);
+      } else {
+        rv = hd_inflate_commit_indname(inflater, nv_out);
+      }
+
+      if (rv != 0) {
+        goto fail;
+      }
+
+      inflater->state = NGHTTP2_HD_STATE_OPCODE;
+      *inflate_flags |= NGHTTP2_HD_INFLATE_EMIT;
+
+      return (ssize_t)(in - first);
+    }
+  }
+
+  assert(in == last);
+
+  DEBUGF(fprintf(stderr, "inflatehd: all input bytes were processed\n"));
+
+  if (in_final) {
+    DEBUGF(fprintf(stderr, "inflatehd: in_final set\n"));
+
+    if (inflater->state != NGHTTP2_HD_STATE_OPCODE) {
+      DEBUGF(fprintf(stderr, "inflatehd: unacceptable state=%d\n",
+                     inflater->state));
+      rv = NGHTTP2_ERR_HEADER_COMP;
+
+      goto fail;
+    }
+    *inflate_flags |= NGHTTP2_HD_INFLATE_FINAL;
+  }
+  return (ssize_t)(in - first);
+
+almost_ok:
+  if (in_final && inflater->state != NGHTTP2_HD_STATE_OPCODE) {
+    DEBUGF(fprintf(stderr, "inflatehd: input ended prematurely\n"));
+
+    rv = NGHTTP2_ERR_HEADER_COMP;
+
+    goto fail;
+  }
+  return (ssize_t)(in - first);
+
+fail:
+  DEBUGF(fprintf(stderr, "inflatehd: error return %zd\n", rv));
+
+  inflater->ctx.bad = 1;
+  return rv;
+}
+
+int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater) {
+  hd_inflate_keep_free(inflater);
+  return 0;
+}
+
+int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr) {
+  return nghttp2_hd_inflate_new2(inflater_ptr, NULL);
+}
+
+int nghttp2_hd_inflate_new2(nghttp2_hd_inflater **inflater_ptr,
+                            nghttp2_mem *mem) {
+  int rv;
+  nghttp2_hd_inflater *inflater;
+
+  if (mem == NULL) {
+    mem = nghttp2_mem_default();
+  }
+
+  inflater = nghttp2_mem_malloc(mem, sizeof(nghttp2_hd_inflater));
+
+  if (inflater == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  rv = nghttp2_hd_inflate_init(inflater, mem);
+
+  if (rv != 0) {
+    nghttp2_mem_free(mem, inflater);
+
+    return rv;
+  }
+
+  *inflater_ptr = inflater;
+
+  return 0;
+}
+
+void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater) {
+  nghttp2_mem *mem;
+
+  mem = inflater->ctx.mem;
+  nghttp2_hd_inflate_free(inflater);
+
+  nghttp2_mem_free(mem, inflater);
+}
+
+int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t idx,
+                                  nghttp2_nv *nv, int inc_indexing) {
+
+  return emit_indname_block(bufs, idx, nv, inc_indexing);
+}
+
+int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
+                                  int inc_indexing) {
+  return emit_newname_block(bufs, nv, inc_indexing);
+}
+
+int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size) {
+  return emit_table_size(bufs, table_size);
+}
+
+ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final,
+                                 uint32_t initial, size_t shift, uint8_t *in,
+                                 uint8_t *last, size_t prefix) {
+  return decode_length(res, shift_ptr, final, initial, shift, in, last, prefix);
+}
diff --git a/lib/nghttp2_hd.h b/lib/nghttp2_hd.h
new file mode 100644 (file)
index 0000000..178abe3
--- /dev/null
@@ -0,0 +1,349 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_HD_H
+#define NGHTTP2_HD_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+#include "nghttp2_hd_huffman.h"
+#include "nghttp2_buf.h"
+#include "nghttp2_mem.h"
+
+#define NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE NGHTTP2_DEFAULT_HEADER_TABLE_SIZE
+#define NGHTTP2_HD_ENTRY_OVERHEAD 32
+
+/* The maximum length of one name/value pair.  This is the sum of the
+   length of name and value.  This is not specified by the spec. We
+   just chose the arbitrary size */
+#define NGHTTP2_HD_MAX_NV 65536
+
+/* Default size of maximum table buffer size for encoder. Even if
+   remote decoder notifies larger buffer size for its decoding,
+   encoder only uses the memory up to this value. */
+#define NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE (1 << 12)
+
+/* Exported for unit test */
+extern const size_t NGHTTP2_STATIC_TABLE_LENGTH;
+
+typedef enum {
+  NGHTTP2_HD_FLAG_NONE = 0,
+  /* Indicates name was dynamically allocated and must be freed */
+  NGHTTP2_HD_FLAG_NAME_ALLOC = 1,
+  /* Indicates value was dynamically allocated and must be freed */
+  NGHTTP2_HD_FLAG_VALUE_ALLOC = 1 << 1,
+  /* Indicates that the name was gifted to the entry and no copying
+     necessary. */
+  NGHTTP2_HD_FLAG_NAME_GIFT = 1 << 2,
+  /* Indicates that the value was gifted to the entry and no copying
+     necessary. */
+  NGHTTP2_HD_FLAG_VALUE_GIFT = 1 << 3
+} nghttp2_hd_flags;
+
+typedef struct {
+  nghttp2_nv nv;
+  uint32_t name_hash;
+  uint32_t value_hash;
+  /* Reference count */
+  uint8_t ref;
+  uint8_t flags;
+} nghttp2_hd_entry;
+
+typedef struct {
+  nghttp2_hd_entry ent;
+  size_t index;
+} nghttp2_hd_static_entry;
+
+typedef struct {
+  nghttp2_hd_entry **buffer;
+  size_t mask;
+  size_t first;
+  size_t len;
+} nghttp2_hd_ringbuf;
+
+typedef enum {
+  NGHTTP2_HD_OPCODE_NONE,
+  NGHTTP2_HD_OPCODE_INDEXED,
+  NGHTTP2_HD_OPCODE_NEWNAME,
+  NGHTTP2_HD_OPCODE_INDNAME
+} nghttp2_hd_opcode;
+
+typedef enum {
+  NGHTTP2_HD_STATE_OPCODE,
+  NGHTTP2_HD_STATE_READ_TABLE_SIZE,
+  NGHTTP2_HD_STATE_READ_INDEX,
+  NGHTTP2_HD_STATE_NEWNAME_CHECK_NAMELEN,
+  NGHTTP2_HD_STATE_NEWNAME_READ_NAMELEN,
+  NGHTTP2_HD_STATE_NEWNAME_READ_NAMEHUFF,
+  NGHTTP2_HD_STATE_NEWNAME_READ_NAME,
+  NGHTTP2_HD_STATE_CHECK_VALUELEN,
+  NGHTTP2_HD_STATE_READ_VALUELEN,
+  NGHTTP2_HD_STATE_READ_VALUEHUFF,
+  NGHTTP2_HD_STATE_READ_VALUE
+} nghttp2_hd_inflate_state;
+
+typedef struct {
+  /* dynamic header table */
+  nghttp2_hd_ringbuf hd_table;
+  /* Memory allocator */
+  nghttp2_mem *mem;
+  /* Abstract buffer size of hd_table as described in the spec. This
+     is the sum of length of name/value in hd_table +
+     NGHTTP2_HD_ENTRY_OVERHEAD bytes overhead per each entry. */
+  size_t hd_table_bufsize;
+  /* The effective header table size. */
+  size_t hd_table_bufsize_max;
+  /* If inflate/deflate error occurred, this value is set to 1 and
+     further invocation of inflate/deflate will fail with
+     NGHTTP2_ERR_HEADER_COMP. */
+  uint8_t bad;
+} nghttp2_hd_context;
+
+struct nghttp2_hd_deflater {
+  nghttp2_hd_context ctx;
+  /* The upper limit of the header table size the deflater accepts. */
+  size_t deflate_hd_table_bufsize_max;
+  /* Minimum header table size notified in the next context update */
+  size_t min_hd_table_bufsize_max;
+  /* If nonzero, send header table size using encoding context update
+     in the next deflate process */
+  uint8_t notify_table_size_change;
+};
+
+struct nghttp2_hd_inflater {
+  nghttp2_hd_context ctx;
+  /* header buffer */
+  nghttp2_bufs nvbufs;
+  /* Stores current state of huffman decoding */
+  nghttp2_hd_huff_decode_context huff_decode_ctx;
+  /* Pointer to the nghttp2_hd_entry which is used current header
+     emission. This is required because in some cases the
+     ent_keep->ref == 0 and we have to keep track of it. */
+  nghttp2_hd_entry *ent_keep;
+  /* Pointer to the name/value pair buffer which is used in the
+     current header emission. */
+  uint8_t *nv_keep;
+  /* The number of bytes to read */
+  size_t left;
+  /* The index in indexed repr or indexed name */
+  size_t index;
+  /* The length of new name encoded in literal.  For huffman encoded
+     string, this is the length after it is decoded. */
+  size_t newnamelen;
+  /* The maximum header table size the inflater supports. This is the
+     same value transmitted in SETTINGS_HEADER_TABLE_SIZE */
+  size_t settings_hd_table_bufsize_max;
+  /* The number of next shift to decode integer */
+  size_t shift;
+  nghttp2_hd_opcode opcode;
+  nghttp2_hd_inflate_state state;
+  /* nonzero if string is huffman encoded */
+  uint8_t huffman_encoded;
+  /* nonzero if deflater requires that current entry is indexed */
+  uint8_t index_required;
+  /* nonzero if deflater requires that current entry must not be
+     indexed */
+  uint8_t no_index;
+};
+
+/*
+ * Initializes the |ent| members. If NGHTTP2_HD_FLAG_NAME_ALLOC bit
+ * set in the |flags|, the content pointed by the |name| with length
+ * |namelen| is copied. Likewise, if NGHTTP2_HD_FLAG_VALUE_ALLOC bit
+ * set in the |flags|, the content pointed by the |value| with length
+ * |valuelen| is copied.  The |name_hash| and |value_hash| are hash
+ * value for |name| and |value| respectively.  The hash function is
+ * defined in nghttp2_hd.c.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_hd_entry_init(nghttp2_hd_entry *ent, uint8_t flags, uint8_t *name,
+                          size_t namelen, uint8_t *value, size_t valuelen,
+                          uint32_t name_hash, uint32_t value_hash,
+                          nghttp2_mem *mem);
+
+void nghttp2_hd_entry_free(nghttp2_hd_entry *ent, nghttp2_mem *mem);
+
+/*
+ * Initializes |deflater| for deflating name/values pairs.
+ *
+ * The encoder only uses up to
+ * NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE bytes for header table
+ * even if the larger value is specified later in
+ * nghttp2_hd_change_table_size().
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_hd_deflate_init(nghttp2_hd_deflater *deflater, nghttp2_mem *mem);
+
+/*
+ * Initializes |deflater| for deflating name/values pairs.
+ *
+ * The encoder only uses up to |deflate_hd_table_bufsize_max| bytes
+ * for header table even if the larger value is specified later in
+ * nghttp2_hd_change_table_size().
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_hd_deflate_init2(nghttp2_hd_deflater *deflater,
+                             size_t deflate_hd_table_bufsize_max,
+                             nghttp2_mem *mem);
+
+/*
+ * Deallocates any resources allocated for |deflater|.
+ */
+void nghttp2_hd_deflate_free(nghttp2_hd_deflater *deflater);
+
+/*
+ * Deflates the |nva|, which has the |nvlen| name/value pairs, into
+ * the |bufs|.
+ *
+ * This function expands |bufs| as necessary to store the result. If
+ * buffers is full and the process still requires more space, this
+ * funtion fails and returns NGHTTP2_ERR_HEADER_COMP.
+ *
+ * After this function returns, it is safe to delete the |nva|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_HEADER_COMP
+ *     Deflation process has failed.
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+int nghttp2_hd_deflate_hd_bufs(nghttp2_hd_deflater *deflater,
+                               nghttp2_bufs *bufs, const nghttp2_nv *nva,
+                               size_t nvlen);
+
+/*
+ * Initializes |inflater| for inflating name/values pairs.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * :enum:`NGHTTP2_ERR_NOMEM`
+ *     Out of memory.
+ */
+int nghttp2_hd_inflate_init(nghttp2_hd_inflater *inflater, nghttp2_mem *mem);
+
+/*
+ * Deallocates any resources allocated for |inflater|.
+ */
+void nghttp2_hd_inflate_free(nghttp2_hd_inflater *inflater);
+
+/* For unittesting purpose */
+int nghttp2_hd_emit_indname_block(nghttp2_bufs *bufs, size_t index,
+                                  nghttp2_nv *nv, int inc_indexing);
+
+/* For unittesting purpose */
+int nghttp2_hd_emit_newname_block(nghttp2_bufs *bufs, nghttp2_nv *nv,
+                                  int inc_indexing);
+
+/* For unittesting purpose */
+int nghttp2_hd_emit_table_size(nghttp2_bufs *bufs, size_t table_size);
+
+/* For unittesting purpose */
+nghttp2_hd_entry *nghttp2_hd_table_get(nghttp2_hd_context *context,
+                                       size_t index);
+
+/* For unittesting purpose */
+ssize_t nghttp2_hd_decode_length(uint32_t *res, size_t *shift_ptr, int *final,
+                                 uint32_t initial, size_t shift, uint8_t *in,
+                                 uint8_t *last, size_t prefix);
+
+/* Huffman encoding/decoding functions */
+
+/*
+ * Counts the required bytes to encode |src| with length |len|.
+ *
+ * This function returns the number of required bytes to encode given
+ * data, including padding of prefix of terminal symbol code. This
+ * function always succeeds.
+ */
+size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len);
+
+/*
+ * Encodes the given data |src| with length |srclen| to the |bufs|.
+ * This function expands extra buffers in |bufs| if necessary.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Out of buffer space.
+ */
+int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src,
+                           size_t srclen);
+
+void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx);
+
+/*
+ * Decodes the given data |src| with length |srclen|. The |ctx| must
+ * be initialized by nghttp2_hd_huff_decode_context_init(). The result
+ * will be added to |dest|. This function may expand |dest| as
+ * needed. The caller is responsible to release the memory of |dest|
+ * by calling nghttp2_bufs_free() or export its content using
+ * nghttp2_bufs_remove().
+ *
+ * The caller must set the |final| to nonzero if the given input is
+ * the final block.
+ *
+ * This function returns the number of read bytes from the |in|.
+ *
+ * If this function fails, it returns one of the following negative
+ * return codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_BUFFER_ERROR
+ *     Maximum buffer capacity size exceeded.
+ * NGHTTP2_ERR_HEADER_COMP
+ *     Decoding process has failed.
+ */
+ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
+                               nghttp2_bufs *bufs, const uint8_t *src,
+                               size_t srclen, int final);
+
+#endif /* NGHTTP2_HD_H */
diff --git a/lib/nghttp2_hd_huffman.c b/lib/nghttp2_hd_huffman.c
new file mode 100644 (file)
index 0000000..6c4b2b6
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_hd_huffman.h"
+
+#include <string.h>
+#include <assert.h>
+#include <stdio.h>
+
+#include "nghttp2_hd.h"
+
+extern const nghttp2_huff_sym huff_sym_table[];
+extern const nghttp2_huff_decode huff_decode_table[][16];
+
+/*
+ * Encodes huffman code |sym| into |*dest_ptr|, whose least |rembits|
+ * bits are not filled yet.  The |rembits| must be in range [1, 8],
+ * inclusive.  At the end of the process, the |*dest_ptr| is updated
+ * and points where next output should be placed. The number of
+ * unfilled bits in the pointed location is returned.
+ */
+static ssize_t huff_encode_sym(nghttp2_bufs *bufs, size_t *avail_ptr,
+                               size_t rembits, const nghttp2_huff_sym *sym) {
+  int rv;
+  size_t nbits = sym->nbits;
+  uint32_t code = sym->code;
+
+  /* We assume that sym->nbits <= 32 */
+  if (rembits > nbits) {
+    nghttp2_bufs_fast_orb_hold(bufs, code << (rembits - nbits));
+    return (ssize_t)(rembits - nbits);
+  }
+
+  if (rembits == nbits) {
+    nghttp2_bufs_fast_orb(bufs, code);
+    --*avail_ptr;
+    return 8;
+  }
+
+  nghttp2_bufs_fast_orb(bufs, code >> (nbits - rembits));
+  --*avail_ptr;
+
+  nbits -= rembits;
+  if (nbits & 0x7) {
+    /* align code to MSB byte boundary */
+    code <<= 8 - (nbits & 0x7);
+  }
+
+  /* we lose at most 3 bytes, but it is not critical in practice */
+  if (*avail_ptr < (nbits + 7) / 8) {
+    rv = nghttp2_bufs_advance(bufs);
+    if (rv != 0) {
+      return rv;
+    }
+    *avail_ptr = nghttp2_bufs_cur_avail(bufs);
+    /* we assume that we at least 3 buffer space available */
+    assert(*avail_ptr >= 3);
+  }
+
+  /* fast path, since most code is less than 8 */
+  if (nbits < 8) {
+    nghttp2_bufs_fast_addb_hold(bufs, code);
+    *avail_ptr = nghttp2_bufs_cur_avail(bufs);
+    return (ssize_t)(8 - nbits);
+  }
+
+  /* handle longer code path */
+  if (nbits > 24) {
+    nghttp2_bufs_fast_addb(bufs, code >> 24);
+    nbits -= 8;
+  }
+
+  if (nbits > 16) {
+    nghttp2_bufs_fast_addb(bufs, code >> 16);
+    nbits -= 8;
+  }
+
+  if (nbits > 8) {
+    nghttp2_bufs_fast_addb(bufs, code >> 8);
+    nbits -= 8;
+  }
+
+  if (nbits == 8) {
+    nghttp2_bufs_fast_addb(bufs, code);
+    *avail_ptr = nghttp2_bufs_cur_avail(bufs);
+    return 8;
+  }
+
+  nghttp2_bufs_fast_addb_hold(bufs, code);
+  *avail_ptr = nghttp2_bufs_cur_avail(bufs);
+  return (ssize_t)(8 - nbits);
+}
+
+size_t nghttp2_hd_huff_encode_count(const uint8_t *src, size_t len) {
+  size_t i;
+  size_t nbits = 0;
+
+  for (i = 0; i < len; ++i) {
+    nbits += huff_sym_table[src[i]].nbits;
+  }
+  /* pad the prefix of EOS (256) */
+  return (nbits + 7) / 8;
+}
+
+int nghttp2_hd_huff_encode(nghttp2_bufs *bufs, const uint8_t *src,
+                           size_t srclen) {
+  int rv;
+  ssize_t rembits = 8;
+  size_t i;
+  size_t avail;
+
+  avail = nghttp2_bufs_cur_avail(bufs);
+
+  for (i = 0; i < srclen; ++i) {
+    const nghttp2_huff_sym *sym = &huff_sym_table[src[i]];
+    if (rembits == 8) {
+      if (avail) {
+        nghttp2_bufs_fast_addb_hold(bufs, 0);
+      } else {
+        rv = nghttp2_bufs_addb_hold(bufs, 0);
+        if (rv != 0) {
+          return rv;
+        }
+        avail = nghttp2_bufs_cur_avail(bufs);
+      }
+    }
+    rembits = huff_encode_sym(bufs, &avail, rembits, sym);
+    if (rembits < 0) {
+      return (int)rembits;
+    }
+  }
+  /* 256 is special terminal symbol, pad with its prefix */
+  if (rembits < 8) {
+    /* if rembits < 8, we should have at least 1 buffer space
+       available */
+    const nghttp2_huff_sym *sym = &huff_sym_table[256];
+    assert(avail);
+    /* Caution we no longer adjust avail here */
+    nghttp2_bufs_fast_orb(bufs, sym->code >> (sym->nbits - rembits));
+  }
+
+  return 0;
+}
+
+void nghttp2_hd_huff_decode_context_init(nghttp2_hd_huff_decode_context *ctx) {
+  ctx->state = 0;
+  ctx->accept = 1;
+}
+
+ssize_t nghttp2_hd_huff_decode(nghttp2_hd_huff_decode_context *ctx,
+                               nghttp2_bufs *bufs, const uint8_t *src,
+                               size_t srclen, int final) {
+  size_t i, j;
+  int rv;
+  size_t avail;
+
+  avail = nghttp2_bufs_cur_avail(bufs);
+
+  /* We use the decoding algorithm described in
+     http://graphics.ics.uci.edu/pub/Prefix.pdf */
+  for (i = 0; i < srclen; ++i) {
+    uint8_t in = src[i] >> 4;
+    for (j = 0; j < 2; ++j) {
+      const nghttp2_huff_decode *t;
+
+      t = &huff_decode_table[ctx->state][in];
+      if (t->flags & NGHTTP2_HUFF_FAIL) {
+        return NGHTTP2_ERR_HEADER_COMP;
+      }
+      if (t->flags & NGHTTP2_HUFF_SYM) {
+        if (avail) {
+          nghttp2_bufs_fast_addb(bufs, t->sym);
+          --avail;
+        } else {
+          rv = nghttp2_bufs_addb(bufs, t->sym);
+          if (rv != 0) {
+            return rv;
+          }
+          avail = nghttp2_bufs_cur_avail(bufs);
+        }
+      }
+      ctx->state = t->state;
+      ctx->accept = (t->flags & NGHTTP2_HUFF_ACCEPTED) != 0;
+      in = src[i] & 0xf;
+    }
+  }
+  if (final && !ctx->accept) {
+    return NGHTTP2_ERR_HEADER_COMP;
+  }
+  return (ssize_t)i;
+}
diff --git a/lib/nghttp2_hd_huffman.h b/lib/nghttp2_hd_huffman.h
new file mode 100644 (file)
index 0000000..714b6b6
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_HD_HUFFMAN_H
+#define NGHTTP2_HD_HUFFMAN_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+typedef enum {
+  /* FSA accepts this state as the end of huffman encoding
+     sequence. */
+  NGHTTP2_HUFF_ACCEPTED = 1,
+  /* This state emits symbol */
+  NGHTTP2_HUFF_SYM = (1 << 1),
+  /* If state machine reaches this state, decoding fails. */
+  NGHTTP2_HUFF_FAIL = (1 << 2)
+} nghttp2_huff_decode_flag;
+
+typedef struct {
+  /* huffman decoding state, which is actually the node ID of internal
+     huffman tree.  We have 257 leaf nodes, but they are identical to
+     root node other than emitting a symbol, so we have 256 internal
+     nodes [1..255], inclusive. */
+  uint8_t state;
+  /* bitwise OR of zero or more of the nghttp2_huff_decode_flag */
+  uint8_t flags;
+  /* symbol if NGHTTP2_HUFF_SYM flag set */
+  uint8_t sym;
+} nghttp2_huff_decode;
+
+typedef nghttp2_huff_decode huff_decode_table_type[16];
+
+typedef struct {
+  /* Current huffman decoding state. We stripped leaf nodes, so the
+     value range is [0..255], inclusive. */
+  uint8_t state;
+  /* nonzero if we can say that the decoding process succeeds at this
+     state */
+  uint8_t accept;
+} nghttp2_hd_huff_decode_context;
+
+typedef struct {
+  /* The number of bits in this code */
+  uint32_t nbits;
+  /* Huffman code aligned to LSB */
+  uint32_t code;
+} nghttp2_huff_sym;
+
+#endif /* NGHTTP2_HD_HUFFMAN_H */
diff --git a/lib/nghttp2_hd_huffman_data.c b/lib/nghttp2_hd_huffman_data.c
new file mode 100644 (file)
index 0000000..4a4251b
--- /dev/null
@@ -0,0 +1,5152 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_hd_huffman.h"
+
+/* Generated by mkhufftbl.py */
+
+const nghttp2_huff_sym huff_sym_table[] = {{13, 0x1ff8u},
+                                           {23, 0x7fffd8u},
+                                           {28, 0xfffffe2u},
+                                           {28, 0xfffffe3u},
+                                           {28, 0xfffffe4u},
+                                           {28, 0xfffffe5u},
+                                           {28, 0xfffffe6u},
+                                           {28, 0xfffffe7u},
+                                           {28, 0xfffffe8u},
+                                           {24, 0xffffeau},
+                                           {30, 0x3ffffffcu},
+                                           {28, 0xfffffe9u},
+                                           {28, 0xfffffeau},
+                                           {30, 0x3ffffffdu},
+                                           {28, 0xfffffebu},
+                                           {28, 0xfffffecu},
+                                           {28, 0xfffffedu},
+                                           {28, 0xfffffeeu},
+                                           {28, 0xfffffefu},
+                                           {28, 0xffffff0u},
+                                           {28, 0xffffff1u},
+                                           {28, 0xffffff2u},
+                                           {30, 0x3ffffffeu},
+                                           {28, 0xffffff3u},
+                                           {28, 0xffffff4u},
+                                           {28, 0xffffff5u},
+                                           {28, 0xffffff6u},
+                                           {28, 0xffffff7u},
+                                           {28, 0xffffff8u},
+                                           {28, 0xffffff9u},
+                                           {28, 0xffffffau},
+                                           {28, 0xffffffbu},
+                                           {6, 0x14u},
+                                           {10, 0x3f8u},
+                                           {10, 0x3f9u},
+                                           {12, 0xffau},
+                                           {13, 0x1ff9u},
+                                           {6, 0x15u},
+                                           {8, 0xf8u},
+                                           {11, 0x7fau},
+                                           {10, 0x3fau},
+                                           {10, 0x3fbu},
+                                           {8, 0xf9u},
+                                           {11, 0x7fbu},
+                                           {8, 0xfau},
+                                           {6, 0x16u},
+                                           {6, 0x17u},
+                                           {6, 0x18u},
+                                           {5, 0x0u},
+                                           {5, 0x1u},
+                                           {5, 0x2u},
+                                           {6, 0x19u},
+                                           {6, 0x1au},
+                                           {6, 0x1bu},
+                                           {6, 0x1cu},
+                                           {6, 0x1du},
+                                           {6, 0x1eu},
+                                           {6, 0x1fu},
+                                           {7, 0x5cu},
+                                           {8, 0xfbu},
+                                           {15, 0x7ffcu},
+                                           {6, 0x20u},
+                                           {12, 0xffbu},
+                                           {10, 0x3fcu},
+                                           {13, 0x1ffau},
+                                           {6, 0x21u},
+                                           {7, 0x5du},
+                                           {7, 0x5eu},
+                                           {7, 0x5fu},
+                                           {7, 0x60u},
+                                           {7, 0x61u},
+                                           {7, 0x62u},
+                                           {7, 0x63u},
+                                           {7, 0x64u},
+                                           {7, 0x65u},
+                                           {7, 0x66u},
+                                           {7, 0x67u},
+                                           {7, 0x68u},
+                                           {7, 0x69u},
+                                           {7, 0x6au},
+                                           {7, 0x6bu},
+                                           {7, 0x6cu},
+                                           {7, 0x6du},
+                                           {7, 0x6eu},
+                                           {7, 0x6fu},
+                                           {7, 0x70u},
+                                           {7, 0x71u},
+                                           {7, 0x72u},
+                                           {8, 0xfcu},
+                                           {7, 0x73u},
+                                           {8, 0xfdu},
+                                           {13, 0x1ffbu},
+                                           {19, 0x7fff0u},
+                                           {13, 0x1ffcu},
+                                           {14, 0x3ffcu},
+                                           {6, 0x22u},
+                                           {15, 0x7ffdu},
+                                           {5, 0x3u},
+                                           {6, 0x23u},
+                                           {5, 0x4u},
+                                           {6, 0x24u},
+                                           {5, 0x5u},
+                                           {6, 0x25u},
+                                           {6, 0x26u},
+                                           {6, 0x27u},
+                                           {5, 0x6u},
+                                           {7, 0x74u},
+                                           {7, 0x75u},
+                                           {6, 0x28u},
+                                           {6, 0x29u},
+                                           {6, 0x2au},
+                                           {5, 0x7u},
+                                           {6, 0x2bu},
+                                           {7, 0x76u},
+                                           {6, 0x2cu},
+                                           {5, 0x8u},
+                                           {5, 0x9u},
+                                           {6, 0x2du},
+                                           {7, 0x77u},
+                                           {7, 0x78u},
+                                           {7, 0x79u},
+                                           {7, 0x7au},
+                                           {7, 0x7bu},
+                                           {15, 0x7ffeu},
+                                           {11, 0x7fcu},
+                                           {14, 0x3ffdu},
+                                           {13, 0x1ffdu},
+                                           {28, 0xffffffcu},
+                                           {20, 0xfffe6u},
+                                           {22, 0x3fffd2u},
+                                           {20, 0xfffe7u},
+                                           {20, 0xfffe8u},
+                                           {22, 0x3fffd3u},
+                                           {22, 0x3fffd4u},
+                                           {22, 0x3fffd5u},
+                                           {23, 0x7fffd9u},
+                                           {22, 0x3fffd6u},
+                                           {23, 0x7fffdau},
+                                           {23, 0x7fffdbu},
+                                           {23, 0x7fffdcu},
+                                           {23, 0x7fffddu},
+                                           {23, 0x7fffdeu},
+                                           {24, 0xffffebu},
+                                           {23, 0x7fffdfu},
+                                           {24, 0xffffecu},
+                                           {24, 0xffffedu},
+                                           {22, 0x3fffd7u},
+                                           {23, 0x7fffe0u},
+                                           {24, 0xffffeeu},
+                                           {23, 0x7fffe1u},
+                                           {23, 0x7fffe2u},
+                                           {23, 0x7fffe3u},
+                                           {23, 0x7fffe4u},
+                                           {21, 0x1fffdcu},
+                                           {22, 0x3fffd8u},
+                                           {23, 0x7fffe5u},
+                                           {22, 0x3fffd9u},
+                                           {23, 0x7fffe6u},
+                                           {23, 0x7fffe7u},
+                                           {24, 0xffffefu},
+                                           {22, 0x3fffdau},
+                                           {21, 0x1fffddu},
+                                           {20, 0xfffe9u},
+                                           {22, 0x3fffdbu},
+                                           {22, 0x3fffdcu},
+                                           {23, 0x7fffe8u},
+                                           {23, 0x7fffe9u},
+                                           {21, 0x1fffdeu},
+                                           {23, 0x7fffeau},
+                                           {22, 0x3fffddu},
+                                           {22, 0x3fffdeu},
+                                           {24, 0xfffff0u},
+                                           {21, 0x1fffdfu},
+                                           {22, 0x3fffdfu},
+                                           {23, 0x7fffebu},
+                                           {23, 0x7fffecu},
+                                           {21, 0x1fffe0u},
+                                           {21, 0x1fffe1u},
+                                           {22, 0x3fffe0u},
+                                           {21, 0x1fffe2u},
+                                           {23, 0x7fffedu},
+                                           {22, 0x3fffe1u},
+                                           {23, 0x7fffeeu},
+                                           {23, 0x7fffefu},
+                                           {20, 0xfffeau},
+                                           {22, 0x3fffe2u},
+                                           {22, 0x3fffe3u},
+                                           {22, 0x3fffe4u},
+                                           {23, 0x7ffff0u},
+                                           {22, 0x3fffe5u},
+                                           {22, 0x3fffe6u},
+                                           {23, 0x7ffff1u},
+                                           {26, 0x3ffffe0u},
+                                           {26, 0x3ffffe1u},
+                                           {20, 0xfffebu},
+                                           {19, 0x7fff1u},
+                                           {22, 0x3fffe7u},
+                                           {23, 0x7ffff2u},
+                                           {22, 0x3fffe8u},
+                                           {25, 0x1ffffecu},
+                                           {26, 0x3ffffe2u},
+                                           {26, 0x3ffffe3u},
+                                           {26, 0x3ffffe4u},
+                                           {27, 0x7ffffdeu},
+                                           {27, 0x7ffffdfu},
+                                           {26, 0x3ffffe5u},
+                                           {24, 0xfffff1u},
+                                           {25, 0x1ffffedu},
+                                           {19, 0x7fff2u},
+                                           {21, 0x1fffe3u},
+                                           {26, 0x3ffffe6u},
+                                           {27, 0x7ffffe0u},
+                                           {27, 0x7ffffe1u},
+                                           {26, 0x3ffffe7u},
+                                           {27, 0x7ffffe2u},
+                                           {24, 0xfffff2u},
+                                           {21, 0x1fffe4u},
+                                           {21, 0x1fffe5u},
+                                           {26, 0x3ffffe8u},
+                                           {26, 0x3ffffe9u},
+                                           {28, 0xffffffdu},
+                                           {27, 0x7ffffe3u},
+                                           {27, 0x7ffffe4u},
+                                           {27, 0x7ffffe5u},
+                                           {20, 0xfffecu},
+                                           {24, 0xfffff3u},
+                                           {20, 0xfffedu},
+                                           {21, 0x1fffe6u},
+                                           {22, 0x3fffe9u},
+                                           {21, 0x1fffe7u},
+                                           {21, 0x1fffe8u},
+                                           {23, 0x7ffff3u},
+                                           {22, 0x3fffeau},
+                                           {22, 0x3fffebu},
+                                           {25, 0x1ffffeeu},
+                                           {25, 0x1ffffefu},
+                                           {24, 0xfffff4u},
+                                           {24, 0xfffff5u},
+                                           {26, 0x3ffffeau},
+                                           {23, 0x7ffff4u},
+                                           {26, 0x3ffffebu},
+                                           {27, 0x7ffffe6u},
+                                           {26, 0x3ffffecu},
+                                           {26, 0x3ffffedu},
+                                           {27, 0x7ffffe7u},
+                                           {27, 0x7ffffe8u},
+                                           {27, 0x7ffffe9u},
+                                           {27, 0x7ffffeau},
+                                           {27, 0x7ffffebu},
+                                           {28, 0xffffffeu},
+                                           {27, 0x7ffffecu},
+                                           {27, 0x7ffffedu},
+                                           {27, 0x7ffffeeu},
+                                           {27, 0x7ffffefu},
+                                           {27, 0x7fffff0u},
+                                           {26, 0x3ffffeeu},
+                                           {30, 0x3fffffffu}};
+
+const nghttp2_huff_decode huff_decode_table[][16] = {
+    /* 0 */
+    {
+     {4, 0x00, 0},
+     {5, 0x00, 0},
+     {7, 0x00, 0},
+     {8, 0x00, 0},
+     {11, 0x00, 0},
+     {12, 0x00, 0},
+     {16, 0x00, 0},
+     {19, 0x00, 0},
+     {25, 0x00, 0},
+     {28, 0x00, 0},
+     {32, 0x00, 0},
+     {35, 0x00, 0},
+     {42, 0x00, 0},
+     {49, 0x00, 0},
+     {57, 0x00, 0},
+     {64, 0x01, 0},
+    },
+    /* 1 */
+    {
+     {0, 0x03, 48},
+     {0, 0x03, 49},
+     {0, 0x03, 50},
+     {0, 0x03, 97},
+     {0, 0x03, 99},
+     {0, 0x03, 101},
+     {0, 0x03, 105},
+     {0, 0x03, 111},
+     {0, 0x03, 115},
+     {0, 0x03, 116},
+     {13, 0x00, 0},
+     {14, 0x00, 0},
+     {17, 0x00, 0},
+     {18, 0x00, 0},
+     {20, 0x00, 0},
+     {21, 0x00, 0},
+    },
+    /* 2 */
+    {
+     {1, 0x02, 48},
+     {22, 0x03, 48},
+     {1, 0x02, 49},
+     {22, 0x03, 49},
+     {1, 0x02, 50},
+     {22, 0x03, 50},
+     {1, 0x02, 97},
+     {22, 0x03, 97},
+     {1, 0x02, 99},
+     {22, 0x03, 99},
+     {1, 0x02, 101},
+     {22, 0x03, 101},
+     {1, 0x02, 105},
+     {22, 0x03, 105},
+     {1, 0x02, 111},
+     {22, 0x03, 111},
+    },
+    /* 3 */
+    {
+     {2, 0x02, 48},
+     {9, 0x02, 48},
+     {23, 0x02, 48},
+     {40, 0x03, 48},
+     {2, 0x02, 49},
+     {9, 0x02, 49},
+     {23, 0x02, 49},
+     {40, 0x03, 49},
+     {2, 0x02, 50},
+     {9, 0x02, 50},
+     {23, 0x02, 50},
+     {40, 0x03, 50},
+     {2, 0x02, 97},
+     {9, 0x02, 97},
+     {23, 0x02, 97},
+     {40, 0x03, 97},
+    },
+    /* 4 */
+    {
+     {3, 0x02, 48},
+     {6, 0x02, 48},
+     {10, 0x02, 48},
+     {15, 0x02, 48},
+     {24, 0x02, 48},
+     {31, 0x02, 48},
+     {41, 0x02, 48},
+     {56, 0x03, 48},
+     {3, 0x02, 49},
+     {6, 0x02, 49},
+     {10, 0x02, 49},
+     {15, 0x02, 49},
+     {24, 0x02, 49},
+     {31, 0x02, 49},
+     {41, 0x02, 49},
+     {56, 0x03, 49},
+    },
+    /* 5 */
+    {
+     {3, 0x02, 50},
+     {6, 0x02, 50},
+     {10, 0x02, 50},
+     {15, 0x02, 50},
+     {24, 0x02, 50},
+     {31, 0x02, 50},
+     {41, 0x02, 50},
+     {56, 0x03, 50},
+     {3, 0x02, 97},
+     {6, 0x02, 97},
+     {10, 0x02, 97},
+     {15, 0x02, 97},
+     {24, 0x02, 97},
+     {31, 0x02, 97},
+     {41, 0x02, 97},
+     {56, 0x03, 97},
+    },
+    /* 6 */
+    {
+     {2, 0x02, 99},
+     {9, 0x02, 99},
+     {23, 0x02, 99},
+     {40, 0x03, 99},
+     {2, 0x02, 101},
+     {9, 0x02, 101},
+     {23, 0x02, 101},
+     {40, 0x03, 101},
+     {2, 0x02, 105},
+     {9, 0x02, 105},
+     {23, 0x02, 105},
+     {40, 0x03, 105},
+     {2, 0x02, 111},
+     {9, 0x02, 111},
+     {23, 0x02, 111},
+     {40, 0x03, 111},
+    },
+    /* 7 */
+    {
+     {3, 0x02, 99},
+     {6, 0x02, 99},
+     {10, 0x02, 99},
+     {15, 0x02, 99},
+     {24, 0x02, 99},
+     {31, 0x02, 99},
+     {41, 0x02, 99},
+     {56, 0x03, 99},
+     {3, 0x02, 101},
+     {6, 0x02, 101},
+     {10, 0x02, 101},
+     {15, 0x02, 101},
+     {24, 0x02, 101},
+     {31, 0x02, 101},
+     {41, 0x02, 101},
+     {56, 0x03, 101},
+    },
+    /* 8 */
+    {
+     {3, 0x02, 105},
+     {6, 0x02, 105},
+     {10, 0x02, 105},
+     {15, 0x02, 105},
+     {24, 0x02, 105},
+     {31, 0x02, 105},
+     {41, 0x02, 105},
+     {56, 0x03, 105},
+     {3, 0x02, 111},
+     {6, 0x02, 111},
+     {10, 0x02, 111},
+     {15, 0x02, 111},
+     {24, 0x02, 111},
+     {31, 0x02, 111},
+     {41, 0x02, 111},
+     {56, 0x03, 111},
+    },
+    /* 9 */
+    {
+     {1, 0x02, 115},
+     {22, 0x03, 115},
+     {1, 0x02, 116},
+     {22, 0x03, 116},
+     {0, 0x03, 32},
+     {0, 0x03, 37},
+     {0, 0x03, 45},
+     {0, 0x03, 46},
+     {0, 0x03, 47},
+     {0, 0x03, 51},
+     {0, 0x03, 52},
+     {0, 0x03, 53},
+     {0, 0x03, 54},
+     {0, 0x03, 55},
+     {0, 0x03, 56},
+     {0, 0x03, 57},
+    },
+    /* 10 */
+    {
+     {2, 0x02, 115},
+     {9, 0x02, 115},
+     {23, 0x02, 115},
+     {40, 0x03, 115},
+     {2, 0x02, 116},
+     {9, 0x02, 116},
+     {23, 0x02, 116},
+     {40, 0x03, 116},
+     {1, 0x02, 32},
+     {22, 0x03, 32},
+     {1, 0x02, 37},
+     {22, 0x03, 37},
+     {1, 0x02, 45},
+     {22, 0x03, 45},
+     {1, 0x02, 46},
+     {22, 0x03, 46},
+    },
+    /* 11 */
+    {
+     {3, 0x02, 115},
+     {6, 0x02, 115},
+     {10, 0x02, 115},
+     {15, 0x02, 115},
+     {24, 0x02, 115},
+     {31, 0x02, 115},
+     {41, 0x02, 115},
+     {56, 0x03, 115},
+     {3, 0x02, 116},
+     {6, 0x02, 116},
+     {10, 0x02, 116},
+     {15, 0x02, 116},
+     {24, 0x02, 116},
+     {31, 0x02, 116},
+     {41, 0x02, 116},
+     {56, 0x03, 116},
+    },
+    /* 12 */
+    {
+     {2, 0x02, 32},
+     {9, 0x02, 32},
+     {23, 0x02, 32},
+     {40, 0x03, 32},
+     {2, 0x02, 37},
+     {9, 0x02, 37},
+     {23, 0x02, 37},
+     {40, 0x03, 37},
+     {2, 0x02, 45},
+     {9, 0x02, 45},
+     {23, 0x02, 45},
+     {40, 0x03, 45},
+     {2, 0x02, 46},
+     {9, 0x02, 46},
+     {23, 0x02, 46},
+     {40, 0x03, 46},
+    },
+    /* 13 */
+    {
+     {3, 0x02, 32},
+     {6, 0x02, 32},
+     {10, 0x02, 32},
+     {15, 0x02, 32},
+     {24, 0x02, 32},
+     {31, 0x02, 32},
+     {41, 0x02, 32},
+     {56, 0x03, 32},
+     {3, 0x02, 37},
+     {6, 0x02, 37},
+     {10, 0x02, 37},
+     {15, 0x02, 37},
+     {24, 0x02, 37},
+     {31, 0x02, 37},
+     {41, 0x02, 37},
+     {56, 0x03, 37},
+    },
+    /* 14 */
+    {
+     {3, 0x02, 45},
+     {6, 0x02, 45},
+     {10, 0x02, 45},
+     {15, 0x02, 45},
+     {24, 0x02, 45},
+     {31, 0x02, 45},
+     {41, 0x02, 45},
+     {56, 0x03, 45},
+     {3, 0x02, 46},
+     {6, 0x02, 46},
+     {10, 0x02, 46},
+     {15, 0x02, 46},
+     {24, 0x02, 46},
+     {31, 0x02, 46},
+     {41, 0x02, 46},
+     {56, 0x03, 46},
+    },
+    /* 15 */
+    {
+     {1, 0x02, 47},
+     {22, 0x03, 47},
+     {1, 0x02, 51},
+     {22, 0x03, 51},
+     {1, 0x02, 52},
+     {22, 0x03, 52},
+     {1, 0x02, 53},
+     {22, 0x03, 53},
+     {1, 0x02, 54},
+     {22, 0x03, 54},
+     {1, 0x02, 55},
+     {22, 0x03, 55},
+     {1, 0x02, 56},
+     {22, 0x03, 56},
+     {1, 0x02, 57},
+     {22, 0x03, 57},
+    },
+    /* 16 */
+    {
+     {2, 0x02, 47},
+     {9, 0x02, 47},
+     {23, 0x02, 47},
+     {40, 0x03, 47},
+     {2, 0x02, 51},
+     {9, 0x02, 51},
+     {23, 0x02, 51},
+     {40, 0x03, 51},
+     {2, 0x02, 52},
+     {9, 0x02, 52},
+     {23, 0x02, 52},
+     {40, 0x03, 52},
+     {2, 0x02, 53},
+     {9, 0x02, 53},
+     {23, 0x02, 53},
+     {40, 0x03, 53},
+    },
+    /* 17 */
+    {
+     {3, 0x02, 47},
+     {6, 0x02, 47},
+     {10, 0x02, 47},
+     {15, 0x02, 47},
+     {24, 0x02, 47},
+     {31, 0x02, 47},
+     {41, 0x02, 47},
+     {56, 0x03, 47},
+     {3, 0x02, 51},
+     {6, 0x02, 51},
+     {10, 0x02, 51},
+     {15, 0x02, 51},
+     {24, 0x02, 51},
+     {31, 0x02, 51},
+     {41, 0x02, 51},
+     {56, 0x03, 51},
+    },
+    /* 18 */
+    {
+     {3, 0x02, 52},
+     {6, 0x02, 52},
+     {10, 0x02, 52},
+     {15, 0x02, 52},
+     {24, 0x02, 52},
+     {31, 0x02, 52},
+     {41, 0x02, 52},
+     {56, 0x03, 52},
+     {3, 0x02, 53},
+     {6, 0x02, 53},
+     {10, 0x02, 53},
+     {15, 0x02, 53},
+     {24, 0x02, 53},
+     {31, 0x02, 53},
+     {41, 0x02, 53},
+     {56, 0x03, 53},
+    },
+    /* 19 */
+    {
+     {2, 0x02, 54},
+     {9, 0x02, 54},
+     {23, 0x02, 54},
+     {40, 0x03, 54},
+     {2, 0x02, 55},
+     {9, 0x02, 55},
+     {23, 0x02, 55},
+     {40, 0x03, 55},
+     {2, 0x02, 56},
+     {9, 0x02, 56},
+     {23, 0x02, 56},
+     {40, 0x03, 56},
+     {2, 0x02, 57},
+     {9, 0x02, 57},
+     {23, 0x02, 57},
+     {40, 0x03, 57},
+    },
+    /* 20 */
+    {
+     {3, 0x02, 54},
+     {6, 0x02, 54},
+     {10, 0x02, 54},
+     {15, 0x02, 54},
+     {24, 0x02, 54},
+     {31, 0x02, 54},
+     {41, 0x02, 54},
+     {56, 0x03, 54},
+     {3, 0x02, 55},
+     {6, 0x02, 55},
+     {10, 0x02, 55},
+     {15, 0x02, 55},
+     {24, 0x02, 55},
+     {31, 0x02, 55},
+     {41, 0x02, 55},
+     {56, 0x03, 55},
+    },
+    /* 21 */
+    {
+     {3, 0x02, 56},
+     {6, 0x02, 56},
+     {10, 0x02, 56},
+     {15, 0x02, 56},
+     {24, 0x02, 56},
+     {31, 0x02, 56},
+     {41, 0x02, 56},
+     {56, 0x03, 56},
+     {3, 0x02, 57},
+     {6, 0x02, 57},
+     {10, 0x02, 57},
+     {15, 0x02, 57},
+     {24, 0x02, 57},
+     {31, 0x02, 57},
+     {41, 0x02, 57},
+     {56, 0x03, 57},
+    },
+    /* 22 */
+    {
+     {26, 0x00, 0},
+     {27, 0x00, 0},
+     {29, 0x00, 0},
+     {30, 0x00, 0},
+     {33, 0x00, 0},
+     {34, 0x00, 0},
+     {36, 0x00, 0},
+     {37, 0x00, 0},
+     {43, 0x00, 0},
+     {46, 0x00, 0},
+     {50, 0x00, 0},
+     {53, 0x00, 0},
+     {58, 0x00, 0},
+     {61, 0x00, 0},
+     {65, 0x00, 0},
+     {68, 0x01, 0},
+    },
+    /* 23 */
+    {
+     {0, 0x03, 61},
+     {0, 0x03, 65},
+     {0, 0x03, 95},
+     {0, 0x03, 98},
+     {0, 0x03, 100},
+     {0, 0x03, 102},
+     {0, 0x03, 103},
+     {0, 0x03, 104},
+     {0, 0x03, 108},
+     {0, 0x03, 109},
+     {0, 0x03, 110},
+     {0, 0x03, 112},
+     {0, 0x03, 114},
+     {0, 0x03, 117},
+     {38, 0x00, 0},
+     {39, 0x00, 0},
+    },
+    /* 24 */
+    {
+     {1, 0x02, 61},
+     {22, 0x03, 61},
+     {1, 0x02, 65},
+     {22, 0x03, 65},
+     {1, 0x02, 95},
+     {22, 0x03, 95},
+     {1, 0x02, 98},
+     {22, 0x03, 98},
+     {1, 0x02, 100},
+     {22, 0x03, 100},
+     {1, 0x02, 102},
+     {22, 0x03, 102},
+     {1, 0x02, 103},
+     {22, 0x03, 103},
+     {1, 0x02, 104},
+     {22, 0x03, 104},
+    },
+    /* 25 */
+    {
+     {2, 0x02, 61},
+     {9, 0x02, 61},
+     {23, 0x02, 61},
+     {40, 0x03, 61},
+     {2, 0x02, 65},
+     {9, 0x02, 65},
+     {23, 0x02, 65},
+     {40, 0x03, 65},
+     {2, 0x02, 95},
+     {9, 0x02, 95},
+     {23, 0x02, 95},
+     {40, 0x03, 95},
+     {2, 0x02, 98},
+     {9, 0x02, 98},
+     {23, 0x02, 98},
+     {40, 0x03, 98},
+    },
+    /* 26 */
+    {
+     {3, 0x02, 61},
+     {6, 0x02, 61},
+     {10, 0x02, 61},
+     {15, 0x02, 61},
+     {24, 0x02, 61},
+     {31, 0x02, 61},
+     {41, 0x02, 61},
+     {56, 0x03, 61},
+     {3, 0x02, 65},
+     {6, 0x02, 65},
+     {10, 0x02, 65},
+     {15, 0x02, 65},
+     {24, 0x02, 65},
+     {31, 0x02, 65},
+     {41, 0x02, 65},
+     {56, 0x03, 65},
+    },
+    /* 27 */
+    {
+     {3, 0x02, 95},
+     {6, 0x02, 95},
+     {10, 0x02, 95},
+     {15, 0x02, 95},
+     {24, 0x02, 95},
+     {31, 0x02, 95},
+     {41, 0x02, 95},
+     {56, 0x03, 95},
+     {3, 0x02, 98},
+     {6, 0x02, 98},
+     {10, 0x02, 98},
+     {15, 0x02, 98},
+     {24, 0x02, 98},
+     {31, 0x02, 98},
+     {41, 0x02, 98},
+     {56, 0x03, 98},
+    },
+    /* 28 */
+    {
+     {2, 0x02, 100},
+     {9, 0x02, 100},
+     {23, 0x02, 100},
+     {40, 0x03, 100},
+     {2, 0x02, 102},
+     {9, 0x02, 102},
+     {23, 0x02, 102},
+     {40, 0x03, 102},
+     {2, 0x02, 103},
+     {9, 0x02, 103},
+     {23, 0x02, 103},
+     {40, 0x03, 103},
+     {2, 0x02, 104},
+     {9, 0x02, 104},
+     {23, 0x02, 104},
+     {40, 0x03, 104},
+    },
+    /* 29 */
+    {
+     {3, 0x02, 100},
+     {6, 0x02, 100},
+     {10, 0x02, 100},
+     {15, 0x02, 100},
+     {24, 0x02, 100},
+     {31, 0x02, 100},
+     {41, 0x02, 100},
+     {56, 0x03, 100},
+     {3, 0x02, 102},
+     {6, 0x02, 102},
+     {10, 0x02, 102},
+     {15, 0x02, 102},
+     {24, 0x02, 102},
+     {31, 0x02, 102},
+     {41, 0x02, 102},
+     {56, 0x03, 102},
+    },
+    /* 30 */
+    {
+     {3, 0x02, 103},
+     {6, 0x02, 103},
+     {10, 0x02, 103},
+     {15, 0x02, 103},
+     {24, 0x02, 103},
+     {31, 0x02, 103},
+     {41, 0x02, 103},
+     {56, 0x03, 103},
+     {3, 0x02, 104},
+     {6, 0x02, 104},
+     {10, 0x02, 104},
+     {15, 0x02, 104},
+     {24, 0x02, 104},
+     {31, 0x02, 104},
+     {41, 0x02, 104},
+     {56, 0x03, 104},
+    },
+    /* 31 */
+    {
+     {1, 0x02, 108},
+     {22, 0x03, 108},
+     {1, 0x02, 109},
+     {22, 0x03, 109},
+     {1, 0x02, 110},
+     {22, 0x03, 110},
+     {1, 0x02, 112},
+     {22, 0x03, 112},
+     {1, 0x02, 114},
+     {22, 0x03, 114},
+     {1, 0x02, 117},
+     {22, 0x03, 117},
+     {0, 0x03, 58},
+     {0, 0x03, 66},
+     {0, 0x03, 67},
+     {0, 0x03, 68},
+    },
+    /* 32 */
+    {
+     {2, 0x02, 108},
+     {9, 0x02, 108},
+     {23, 0x02, 108},
+     {40, 0x03, 108},
+     {2, 0x02, 109},
+     {9, 0x02, 109},
+     {23, 0x02, 109},
+     {40, 0x03, 109},
+     {2, 0x02, 110},
+     {9, 0x02, 110},
+     {23, 0x02, 110},
+     {40, 0x03, 110},
+     {2, 0x02, 112},
+     {9, 0x02, 112},
+     {23, 0x02, 112},
+     {40, 0x03, 112},
+    },
+    /* 33 */
+    {
+     {3, 0x02, 108},
+     {6, 0x02, 108},
+     {10, 0x02, 108},
+     {15, 0x02, 108},
+     {24, 0x02, 108},
+     {31, 0x02, 108},
+     {41, 0x02, 108},
+     {56, 0x03, 108},
+     {3, 0x02, 109},
+     {6, 0x02, 109},
+     {10, 0x02, 109},
+     {15, 0x02, 109},
+     {24, 0x02, 109},
+     {31, 0x02, 109},
+     {41, 0x02, 109},
+     {56, 0x03, 109},
+    },
+    /* 34 */
+    {
+     {3, 0x02, 110},
+     {6, 0x02, 110},
+     {10, 0x02, 110},
+     {15, 0x02, 110},
+     {24, 0x02, 110},
+     {31, 0x02, 110},
+     {41, 0x02, 110},
+     {56, 0x03, 110},
+     {3, 0x02, 112},
+     {6, 0x02, 112},
+     {10, 0x02, 112},
+     {15, 0x02, 112},
+     {24, 0x02, 112},
+     {31, 0x02, 112},
+     {41, 0x02, 112},
+     {56, 0x03, 112},
+    },
+    /* 35 */
+    {
+     {2, 0x02, 114},
+     {9, 0x02, 114},
+     {23, 0x02, 114},
+     {40, 0x03, 114},
+     {2, 0x02, 117},
+     {9, 0x02, 117},
+     {23, 0x02, 117},
+     {40, 0x03, 117},
+     {1, 0x02, 58},
+     {22, 0x03, 58},
+     {1, 0x02, 66},
+     {22, 0x03, 66},
+     {1, 0x02, 67},
+     {22, 0x03, 67},
+     {1, 0x02, 68},
+     {22, 0x03, 68},
+    },
+    /* 36 */
+    {
+     {3, 0x02, 114},
+     {6, 0x02, 114},
+     {10, 0x02, 114},
+     {15, 0x02, 114},
+     {24, 0x02, 114},
+     {31, 0x02, 114},
+     {41, 0x02, 114},
+     {56, 0x03, 114},
+     {3, 0x02, 117},
+     {6, 0x02, 117},
+     {10, 0x02, 117},
+     {15, 0x02, 117},
+     {24, 0x02, 117},
+     {31, 0x02, 117},
+     {41, 0x02, 117},
+     {56, 0x03, 117},
+    },
+    /* 37 */
+    {
+     {2, 0x02, 58},
+     {9, 0x02, 58},
+     {23, 0x02, 58},
+     {40, 0x03, 58},
+     {2, 0x02, 66},
+     {9, 0x02, 66},
+     {23, 0x02, 66},
+     {40, 0x03, 66},
+     {2, 0x02, 67},
+     {9, 0x02, 67},
+     {23, 0x02, 67},
+     {40, 0x03, 67},
+     {2, 0x02, 68},
+     {9, 0x02, 68},
+     {23, 0x02, 68},
+     {40, 0x03, 68},
+    },
+    /* 38 */
+    {
+     {3, 0x02, 58},
+     {6, 0x02, 58},
+     {10, 0x02, 58},
+     {15, 0x02, 58},
+     {24, 0x02, 58},
+     {31, 0x02, 58},
+     {41, 0x02, 58},
+     {56, 0x03, 58},
+     {3, 0x02, 66},
+     {6, 0x02, 66},
+     {10, 0x02, 66},
+     {15, 0x02, 66},
+     {24, 0x02, 66},
+     {31, 0x02, 66},
+     {41, 0x02, 66},
+     {56, 0x03, 66},
+    },
+    /* 39 */
+    {
+     {3, 0x02, 67},
+     {6, 0x02, 67},
+     {10, 0x02, 67},
+     {15, 0x02, 67},
+     {24, 0x02, 67},
+     {31, 0x02, 67},
+     {41, 0x02, 67},
+     {56, 0x03, 67},
+     {3, 0x02, 68},
+     {6, 0x02, 68},
+     {10, 0x02, 68},
+     {15, 0x02, 68},
+     {24, 0x02, 68},
+     {31, 0x02, 68},
+     {41, 0x02, 68},
+     {56, 0x03, 68},
+    },
+    /* 40 */
+    {
+     {44, 0x00, 0},
+     {45, 0x00, 0},
+     {47, 0x00, 0},
+     {48, 0x00, 0},
+     {51, 0x00, 0},
+     {52, 0x00, 0},
+     {54, 0x00, 0},
+     {55, 0x00, 0},
+     {59, 0x00, 0},
+     {60, 0x00, 0},
+     {62, 0x00, 0},
+     {63, 0x00, 0},
+     {66, 0x00, 0},
+     {67, 0x00, 0},
+     {69, 0x00, 0},
+     {72, 0x01, 0},
+    },
+    /* 41 */
+    {
+     {0, 0x03, 69},
+     {0, 0x03, 70},
+     {0, 0x03, 71},
+     {0, 0x03, 72},
+     {0, 0x03, 73},
+     {0, 0x03, 74},
+     {0, 0x03, 75},
+     {0, 0x03, 76},
+     {0, 0x03, 77},
+     {0, 0x03, 78},
+     {0, 0x03, 79},
+     {0, 0x03, 80},
+     {0, 0x03, 81},
+     {0, 0x03, 82},
+     {0, 0x03, 83},
+     {0, 0x03, 84},
+    },
+    /* 42 */
+    {
+     {1, 0x02, 69},
+     {22, 0x03, 69},
+     {1, 0x02, 70},
+     {22, 0x03, 70},
+     {1, 0x02, 71},
+     {22, 0x03, 71},
+     {1, 0x02, 72},
+     {22, 0x03, 72},
+     {1, 0x02, 73},
+     {22, 0x03, 73},
+     {1, 0x02, 74},
+     {22, 0x03, 74},
+     {1, 0x02, 75},
+     {22, 0x03, 75},
+     {1, 0x02, 76},
+     {22, 0x03, 76},
+    },
+    /* 43 */
+    {
+     {2, 0x02, 69},
+     {9, 0x02, 69},
+     {23, 0x02, 69},
+     {40, 0x03, 69},
+     {2, 0x02, 70},
+     {9, 0x02, 70},
+     {23, 0x02, 70},
+     {40, 0x03, 70},
+     {2, 0x02, 71},
+     {9, 0x02, 71},
+     {23, 0x02, 71},
+     {40, 0x03, 71},
+     {2, 0x02, 72},
+     {9, 0x02, 72},
+     {23, 0x02, 72},
+     {40, 0x03, 72},
+    },
+    /* 44 */
+    {
+     {3, 0x02, 69},
+     {6, 0x02, 69},
+     {10, 0x02, 69},
+     {15, 0x02, 69},
+     {24, 0x02, 69},
+     {31, 0x02, 69},
+     {41, 0x02, 69},
+     {56, 0x03, 69},
+     {3, 0x02, 70},
+     {6, 0x02, 70},
+     {10, 0x02, 70},
+     {15, 0x02, 70},
+     {24, 0x02, 70},
+     {31, 0x02, 70},
+     {41, 0x02, 70},
+     {56, 0x03, 70},
+    },
+    /* 45 */
+    {
+     {3, 0x02, 71},
+     {6, 0x02, 71},
+     {10, 0x02, 71},
+     {15, 0x02, 71},
+     {24, 0x02, 71},
+     {31, 0x02, 71},
+     {41, 0x02, 71},
+     {56, 0x03, 71},
+     {3, 0x02, 72},
+     {6, 0x02, 72},
+     {10, 0x02, 72},
+     {15, 0x02, 72},
+     {24, 0x02, 72},
+     {31, 0x02, 72},
+     {41, 0x02, 72},
+     {56, 0x03, 72},
+    },
+    /* 46 */
+    {
+     {2, 0x02, 73},
+     {9, 0x02, 73},
+     {23, 0x02, 73},
+     {40, 0x03, 73},
+     {2, 0x02, 74},
+     {9, 0x02, 74},
+     {23, 0x02, 74},
+     {40, 0x03, 74},
+     {2, 0x02, 75},
+     {9, 0x02, 75},
+     {23, 0x02, 75},
+     {40, 0x03, 75},
+     {2, 0x02, 76},
+     {9, 0x02, 76},
+     {23, 0x02, 76},
+     {40, 0x03, 76},
+    },
+    /* 47 */
+    {
+     {3, 0x02, 73},
+     {6, 0x02, 73},
+     {10, 0x02, 73},
+     {15, 0x02, 73},
+     {24, 0x02, 73},
+     {31, 0x02, 73},
+     {41, 0x02, 73},
+     {56, 0x03, 73},
+     {3, 0x02, 74},
+     {6, 0x02, 74},
+     {10, 0x02, 74},
+     {15, 0x02, 74},
+     {24, 0x02, 74},
+     {31, 0x02, 74},
+     {41, 0x02, 74},
+     {56, 0x03, 74},
+    },
+    /* 48 */
+    {
+     {3, 0x02, 75},
+     {6, 0x02, 75},
+     {10, 0x02, 75},
+     {15, 0x02, 75},
+     {24, 0x02, 75},
+     {31, 0x02, 75},
+     {41, 0x02, 75},
+     {56, 0x03, 75},
+     {3, 0x02, 76},
+     {6, 0x02, 76},
+     {10, 0x02, 76},
+     {15, 0x02, 76},
+     {24, 0x02, 76},
+     {31, 0x02, 76},
+     {41, 0x02, 76},
+     {56, 0x03, 76},
+    },
+    /* 49 */
+    {
+     {1, 0x02, 77},
+     {22, 0x03, 77},
+     {1, 0x02, 78},
+     {22, 0x03, 78},
+     {1, 0x02, 79},
+     {22, 0x03, 79},
+     {1, 0x02, 80},
+     {22, 0x03, 80},
+     {1, 0x02, 81},
+     {22, 0x03, 81},
+     {1, 0x02, 82},
+     {22, 0x03, 82},
+     {1, 0x02, 83},
+     {22, 0x03, 83},
+     {1, 0x02, 84},
+     {22, 0x03, 84},
+    },
+    /* 50 */
+    {
+     {2, 0x02, 77},
+     {9, 0x02, 77},
+     {23, 0x02, 77},
+     {40, 0x03, 77},
+     {2, 0x02, 78},
+     {9, 0x02, 78},
+     {23, 0x02, 78},
+     {40, 0x03, 78},
+     {2, 0x02, 79},
+     {9, 0x02, 79},
+     {23, 0x02, 79},
+     {40, 0x03, 79},
+     {2, 0x02, 80},
+     {9, 0x02, 80},
+     {23, 0x02, 80},
+     {40, 0x03, 80},
+    },
+    /* 51 */
+    {
+     {3, 0x02, 77},
+     {6, 0x02, 77},
+     {10, 0x02, 77},
+     {15, 0x02, 77},
+     {24, 0x02, 77},
+     {31, 0x02, 77},
+     {41, 0x02, 77},
+     {56, 0x03, 77},
+     {3, 0x02, 78},
+     {6, 0x02, 78},
+     {10, 0x02, 78},
+     {15, 0x02, 78},
+     {24, 0x02, 78},
+     {31, 0x02, 78},
+     {41, 0x02, 78},
+     {56, 0x03, 78},
+    },
+    /* 52 */
+    {
+     {3, 0x02, 79},
+     {6, 0x02, 79},
+     {10, 0x02, 79},
+     {15, 0x02, 79},
+     {24, 0x02, 79},
+     {31, 0x02, 79},
+     {41, 0x02, 79},
+     {56, 0x03, 79},
+     {3, 0x02, 80},
+     {6, 0x02, 80},
+     {10, 0x02, 80},
+     {15, 0x02, 80},
+     {24, 0x02, 80},
+     {31, 0x02, 80},
+     {41, 0x02, 80},
+     {56, 0x03, 80},
+    },
+    /* 53 */
+    {
+     {2, 0x02, 81},
+     {9, 0x02, 81},
+     {23, 0x02, 81},
+     {40, 0x03, 81},
+     {2, 0x02, 82},
+     {9, 0x02, 82},
+     {23, 0x02, 82},
+     {40, 0x03, 82},
+     {2, 0x02, 83},
+     {9, 0x02, 83},
+     {23, 0x02, 83},
+     {40, 0x03, 83},
+     {2, 0x02, 84},
+     {9, 0x02, 84},
+     {23, 0x02, 84},
+     {40, 0x03, 84},
+    },
+    /* 54 */
+    {
+     {3, 0x02, 81},
+     {6, 0x02, 81},
+     {10, 0x02, 81},
+     {15, 0x02, 81},
+     {24, 0x02, 81},
+     {31, 0x02, 81},
+     {41, 0x02, 81},
+     {56, 0x03, 81},
+     {3, 0x02, 82},
+     {6, 0x02, 82},
+     {10, 0x02, 82},
+     {15, 0x02, 82},
+     {24, 0x02, 82},
+     {31, 0x02, 82},
+     {41, 0x02, 82},
+     {56, 0x03, 82},
+    },
+    /* 55 */
+    {
+     {3, 0x02, 83},
+     {6, 0x02, 83},
+     {10, 0x02, 83},
+     {15, 0x02, 83},
+     {24, 0x02, 83},
+     {31, 0x02, 83},
+     {41, 0x02, 83},
+     {56, 0x03, 83},
+     {3, 0x02, 84},
+     {6, 0x02, 84},
+     {10, 0x02, 84},
+     {15, 0x02, 84},
+     {24, 0x02, 84},
+     {31, 0x02, 84},
+     {41, 0x02, 84},
+     {56, 0x03, 84},
+    },
+    /* 56 */
+    {
+     {0, 0x03, 85},
+     {0, 0x03, 86},
+     {0, 0x03, 87},
+     {0, 0x03, 89},
+     {0, 0x03, 106},
+     {0, 0x03, 107},
+     {0, 0x03, 113},
+     {0, 0x03, 118},
+     {0, 0x03, 119},
+     {0, 0x03, 120},
+     {0, 0x03, 121},
+     {0, 0x03, 122},
+     {70, 0x00, 0},
+     {71, 0x00, 0},
+     {73, 0x00, 0},
+     {74, 0x01, 0},
+    },
+    /* 57 */
+    {
+     {1, 0x02, 85},
+     {22, 0x03, 85},
+     {1, 0x02, 86},
+     {22, 0x03, 86},
+     {1, 0x02, 87},
+     {22, 0x03, 87},
+     {1, 0x02, 89},
+     {22, 0x03, 89},
+     {1, 0x02, 106},
+     {22, 0x03, 106},
+     {1, 0x02, 107},
+     {22, 0x03, 107},
+     {1, 0x02, 113},
+     {22, 0x03, 113},
+     {1, 0x02, 118},
+     {22, 0x03, 118},
+    },
+    /* 58 */
+    {
+     {2, 0x02, 85},
+     {9, 0x02, 85},
+     {23, 0x02, 85},
+     {40, 0x03, 85},
+     {2, 0x02, 86},
+     {9, 0x02, 86},
+     {23, 0x02, 86},
+     {40, 0x03, 86},
+     {2, 0x02, 87},
+     {9, 0x02, 87},
+     {23, 0x02, 87},
+     {40, 0x03, 87},
+     {2, 0x02, 89},
+     {9, 0x02, 89},
+     {23, 0x02, 89},
+     {40, 0x03, 89},
+    },
+    /* 59 */
+    {
+     {3, 0x02, 85},
+     {6, 0x02, 85},
+     {10, 0x02, 85},
+     {15, 0x02, 85},
+     {24, 0x02, 85},
+     {31, 0x02, 85},
+     {41, 0x02, 85},
+     {56, 0x03, 85},
+     {3, 0x02, 86},
+     {6, 0x02, 86},
+     {10, 0x02, 86},
+     {15, 0x02, 86},
+     {24, 0x02, 86},
+     {31, 0x02, 86},
+     {41, 0x02, 86},
+     {56, 0x03, 86},
+    },
+    /* 60 */
+    {
+     {3, 0x02, 87},
+     {6, 0x02, 87},
+     {10, 0x02, 87},
+     {15, 0x02, 87},
+     {24, 0x02, 87},
+     {31, 0x02, 87},
+     {41, 0x02, 87},
+     {56, 0x03, 87},
+     {3, 0x02, 89},
+     {6, 0x02, 89},
+     {10, 0x02, 89},
+     {15, 0x02, 89},
+     {24, 0x02, 89},
+     {31, 0x02, 89},
+     {41, 0x02, 89},
+     {56, 0x03, 89},
+    },
+    /* 61 */
+    {
+     {2, 0x02, 106},
+     {9, 0x02, 106},
+     {23, 0x02, 106},
+     {40, 0x03, 106},
+     {2, 0x02, 107},
+     {9, 0x02, 107},
+     {23, 0x02, 107},
+     {40, 0x03, 107},
+     {2, 0x02, 113},
+     {9, 0x02, 113},
+     {23, 0x02, 113},
+     {40, 0x03, 113},
+     {2, 0x02, 118},
+     {9, 0x02, 118},
+     {23, 0x02, 118},
+     {40, 0x03, 118},
+    },
+    /* 62 */
+    {
+     {3, 0x02, 106},
+     {6, 0x02, 106},
+     {10, 0x02, 106},
+     {15, 0x02, 106},
+     {24, 0x02, 106},
+     {31, 0x02, 106},
+     {41, 0x02, 106},
+     {56, 0x03, 106},
+     {3, 0x02, 107},
+     {6, 0x02, 107},
+     {10, 0x02, 107},
+     {15, 0x02, 107},
+     {24, 0x02, 107},
+     {31, 0x02, 107},
+     {41, 0x02, 107},
+     {56, 0x03, 107},
+    },
+    /* 63 */
+    {
+     {3, 0x02, 113},
+     {6, 0x02, 113},
+     {10, 0x02, 113},
+     {15, 0x02, 113},
+     {24, 0x02, 113},
+     {31, 0x02, 113},
+     {41, 0x02, 113},
+     {56, 0x03, 113},
+     {3, 0x02, 118},
+     {6, 0x02, 118},
+     {10, 0x02, 118},
+     {15, 0x02, 118},
+     {24, 0x02, 118},
+     {31, 0x02, 118},
+     {41, 0x02, 118},
+     {56, 0x03, 118},
+    },
+    /* 64 */
+    {
+     {1, 0x02, 119},
+     {22, 0x03, 119},
+     {1, 0x02, 120},
+     {22, 0x03, 120},
+     {1, 0x02, 121},
+     {22, 0x03, 121},
+     {1, 0x02, 122},
+     {22, 0x03, 122},
+     {0, 0x03, 38},
+     {0, 0x03, 42},
+     {0, 0x03, 44},
+     {0, 0x03, 59},
+     {0, 0x03, 88},
+     {0, 0x03, 90},
+     {75, 0x00, 0},
+     {78, 0x00, 0},
+    },
+    /* 65 */
+    {
+     {2, 0x02, 119},
+     {9, 0x02, 119},
+     {23, 0x02, 119},
+     {40, 0x03, 119},
+     {2, 0x02, 120},
+     {9, 0x02, 120},
+     {23, 0x02, 120},
+     {40, 0x03, 120},
+     {2, 0x02, 121},
+     {9, 0x02, 121},
+     {23, 0x02, 121},
+     {40, 0x03, 121},
+     {2, 0x02, 122},
+     {9, 0x02, 122},
+     {23, 0x02, 122},
+     {40, 0x03, 122},
+    },
+    /* 66 */
+    {
+     {3, 0x02, 119},
+     {6, 0x02, 119},
+     {10, 0x02, 119},
+     {15, 0x02, 119},
+     {24, 0x02, 119},
+     {31, 0x02, 119},
+     {41, 0x02, 119},
+     {56, 0x03, 119},
+     {3, 0x02, 120},
+     {6, 0x02, 120},
+     {10, 0x02, 120},
+     {15, 0x02, 120},
+     {24, 0x02, 120},
+     {31, 0x02, 120},
+     {41, 0x02, 120},
+     {56, 0x03, 120},
+    },
+    /* 67 */
+    {
+     {3, 0x02, 121},
+     {6, 0x02, 121},
+     {10, 0x02, 121},
+     {15, 0x02, 121},
+     {24, 0x02, 121},
+     {31, 0x02, 121},
+     {41, 0x02, 121},
+     {56, 0x03, 121},
+     {3, 0x02, 122},
+     {6, 0x02, 122},
+     {10, 0x02, 122},
+     {15, 0x02, 122},
+     {24, 0x02, 122},
+     {31, 0x02, 122},
+     {41, 0x02, 122},
+     {56, 0x03, 122},
+    },
+    /* 68 */
+    {
+     {1, 0x02, 38},
+     {22, 0x03, 38},
+     {1, 0x02, 42},
+     {22, 0x03, 42},
+     {1, 0x02, 44},
+     {22, 0x03, 44},
+     {1, 0x02, 59},
+     {22, 0x03, 59},
+     {1, 0x02, 88},
+     {22, 0x03, 88},
+     {1, 0x02, 90},
+     {22, 0x03, 90},
+     {76, 0x00, 0},
+     {77, 0x00, 0},
+     {79, 0x00, 0},
+     {81, 0x00, 0},
+    },
+    /* 69 */
+    {
+     {2, 0x02, 38},
+     {9, 0x02, 38},
+     {23, 0x02, 38},
+     {40, 0x03, 38},
+     {2, 0x02, 42},
+     {9, 0x02, 42},
+     {23, 0x02, 42},
+     {40, 0x03, 42},
+     {2, 0x02, 44},
+     {9, 0x02, 44},
+     {23, 0x02, 44},
+     {40, 0x03, 44},
+     {2, 0x02, 59},
+     {9, 0x02, 59},
+     {23, 0x02, 59},
+     {40, 0x03, 59},
+    },
+    /* 70 */
+    {
+     {3, 0x02, 38},
+     {6, 0x02, 38},
+     {10, 0x02, 38},
+     {15, 0x02, 38},
+     {24, 0x02, 38},
+     {31, 0x02, 38},
+     {41, 0x02, 38},
+     {56, 0x03, 38},
+     {3, 0x02, 42},
+     {6, 0x02, 42},
+     {10, 0x02, 42},
+     {15, 0x02, 42},
+     {24, 0x02, 42},
+     {31, 0x02, 42},
+     {41, 0x02, 42},
+     {56, 0x03, 42},
+    },
+    /* 71 */
+    {
+     {3, 0x02, 44},
+     {6, 0x02, 44},
+     {10, 0x02, 44},
+     {15, 0x02, 44},
+     {24, 0x02, 44},
+     {31, 0x02, 44},
+     {41, 0x02, 44},
+     {56, 0x03, 44},
+     {3, 0x02, 59},
+     {6, 0x02, 59},
+     {10, 0x02, 59},
+     {15, 0x02, 59},
+     {24, 0x02, 59},
+     {31, 0x02, 59},
+     {41, 0x02, 59},
+     {56, 0x03, 59},
+    },
+    /* 72 */
+    {
+     {2, 0x02, 88},
+     {9, 0x02, 88},
+     {23, 0x02, 88},
+     {40, 0x03, 88},
+     {2, 0x02, 90},
+     {9, 0x02, 90},
+     {23, 0x02, 90},
+     {40, 0x03, 90},
+     {0, 0x03, 33},
+     {0, 0x03, 34},
+     {0, 0x03, 40},
+     {0, 0x03, 41},
+     {0, 0x03, 63},
+     {80, 0x00, 0},
+     {82, 0x00, 0},
+     {84, 0x00, 0},
+    },
+    /* 73 */
+    {
+     {3, 0x02, 88},
+     {6, 0x02, 88},
+     {10, 0x02, 88},
+     {15, 0x02, 88},
+     {24, 0x02, 88},
+     {31, 0x02, 88},
+     {41, 0x02, 88},
+     {56, 0x03, 88},
+     {3, 0x02, 90},
+     {6, 0x02, 90},
+     {10, 0x02, 90},
+     {15, 0x02, 90},
+     {24, 0x02, 90},
+     {31, 0x02, 90},
+     {41, 0x02, 90},
+     {56, 0x03, 90},
+    },
+    /* 74 */
+    {
+     {1, 0x02, 33},
+     {22, 0x03, 33},
+     {1, 0x02, 34},
+     {22, 0x03, 34},
+     {1, 0x02, 40},
+     {22, 0x03, 40},
+     {1, 0x02, 41},
+     {22, 0x03, 41},
+     {1, 0x02, 63},
+     {22, 0x03, 63},
+     {0, 0x03, 39},
+     {0, 0x03, 43},
+     {0, 0x03, 124},
+     {83, 0x00, 0},
+     {85, 0x00, 0},
+     {88, 0x00, 0},
+    },
+    /* 75 */
+    {
+     {2, 0x02, 33},
+     {9, 0x02, 33},
+     {23, 0x02, 33},
+     {40, 0x03, 33},
+     {2, 0x02, 34},
+     {9, 0x02, 34},
+     {23, 0x02, 34},
+     {40, 0x03, 34},
+     {2, 0x02, 40},
+     {9, 0x02, 40},
+     {23, 0x02, 40},
+     {40, 0x03, 40},
+     {2, 0x02, 41},
+     {9, 0x02, 41},
+     {23, 0x02, 41},
+     {40, 0x03, 41},
+    },
+    /* 76 */
+    {
+     {3, 0x02, 33},
+     {6, 0x02, 33},
+     {10, 0x02, 33},
+     {15, 0x02, 33},
+     {24, 0x02, 33},
+     {31, 0x02, 33},
+     {41, 0x02, 33},
+     {56, 0x03, 33},
+     {3, 0x02, 34},
+     {6, 0x02, 34},
+     {10, 0x02, 34},
+     {15, 0x02, 34},
+     {24, 0x02, 34},
+     {31, 0x02, 34},
+     {41, 0x02, 34},
+     {56, 0x03, 34},
+    },
+    /* 77 */
+    {
+     {3, 0x02, 40},
+     {6, 0x02, 40},
+     {10, 0x02, 40},
+     {15, 0x02, 40},
+     {24, 0x02, 40},
+     {31, 0x02, 40},
+     {41, 0x02, 40},
+     {56, 0x03, 40},
+     {3, 0x02, 41},
+     {6, 0x02, 41},
+     {10, 0x02, 41},
+     {15, 0x02, 41},
+     {24, 0x02, 41},
+     {31, 0x02, 41},
+     {41, 0x02, 41},
+     {56, 0x03, 41},
+    },
+    /* 78 */
+    {
+     {2, 0x02, 63},
+     {9, 0x02, 63},
+     {23, 0x02, 63},
+     {40, 0x03, 63},
+     {1, 0x02, 39},
+     {22, 0x03, 39},
+     {1, 0x02, 43},
+     {22, 0x03, 43},
+     {1, 0x02, 124},
+     {22, 0x03, 124},
+     {0, 0x03, 35},
+     {0, 0x03, 62},
+     {86, 0x00, 0},
+     {87, 0x00, 0},
+     {89, 0x00, 0},
+     {90, 0x00, 0},
+    },
+    /* 79 */
+    {
+     {3, 0x02, 63},
+     {6, 0x02, 63},
+     {10, 0x02, 63},
+     {15, 0x02, 63},
+     {24, 0x02, 63},
+     {31, 0x02, 63},
+     {41, 0x02, 63},
+     {56, 0x03, 63},
+     {2, 0x02, 39},
+     {9, 0x02, 39},
+     {23, 0x02, 39},
+     {40, 0x03, 39},
+     {2, 0x02, 43},
+     {9, 0x02, 43},
+     {23, 0x02, 43},
+     {40, 0x03, 43},
+    },
+    /* 80 */
+    {
+     {3, 0x02, 39},
+     {6, 0x02, 39},
+     {10, 0x02, 39},
+     {15, 0x02, 39},
+     {24, 0x02, 39},
+     {31, 0x02, 39},
+     {41, 0x02, 39},
+     {56, 0x03, 39},
+     {3, 0x02, 43},
+     {6, 0x02, 43},
+     {10, 0x02, 43},
+     {15, 0x02, 43},
+     {24, 0x02, 43},
+     {31, 0x02, 43},
+     {41, 0x02, 43},
+     {56, 0x03, 43},
+    },
+    /* 81 */
+    {
+     {2, 0x02, 124},
+     {9, 0x02, 124},
+     {23, 0x02, 124},
+     {40, 0x03, 124},
+     {1, 0x02, 35},
+     {22, 0x03, 35},
+     {1, 0x02, 62},
+     {22, 0x03, 62},
+     {0, 0x03, 0},
+     {0, 0x03, 36},
+     {0, 0x03, 64},
+     {0, 0x03, 91},
+     {0, 0x03, 93},
+     {0, 0x03, 126},
+     {91, 0x00, 0},
+     {92, 0x00, 0},
+    },
+    /* 82 */
+    {
+     {3, 0x02, 124},
+     {6, 0x02, 124},
+     {10, 0x02, 124},
+     {15, 0x02, 124},
+     {24, 0x02, 124},
+     {31, 0x02, 124},
+     {41, 0x02, 124},
+     {56, 0x03, 124},
+     {2, 0x02, 35},
+     {9, 0x02, 35},
+     {23, 0x02, 35},
+     {40, 0x03, 35},
+     {2, 0x02, 62},
+     {9, 0x02, 62},
+     {23, 0x02, 62},
+     {40, 0x03, 62},
+    },
+    /* 83 */
+    {
+     {3, 0x02, 35},
+     {6, 0x02, 35},
+     {10, 0x02, 35},
+     {15, 0x02, 35},
+     {24, 0x02, 35},
+     {31, 0x02, 35},
+     {41, 0x02, 35},
+     {56, 0x03, 35},
+     {3, 0x02, 62},
+     {6, 0x02, 62},
+     {10, 0x02, 62},
+     {15, 0x02, 62},
+     {24, 0x02, 62},
+     {31, 0x02, 62},
+     {41, 0x02, 62},
+     {56, 0x03, 62},
+    },
+    /* 84 */
+    {
+     {1, 0x02, 0},
+     {22, 0x03, 0},
+     {1, 0x02, 36},
+     {22, 0x03, 36},
+     {1, 0x02, 64},
+     {22, 0x03, 64},
+     {1, 0x02, 91},
+     {22, 0x03, 91},
+     {1, 0x02, 93},
+     {22, 0x03, 93},
+     {1, 0x02, 126},
+     {22, 0x03, 126},
+     {0, 0x03, 94},
+     {0, 0x03, 125},
+     {93, 0x00, 0},
+     {94, 0x00, 0},
+    },
+    /* 85 */
+    {
+     {2, 0x02, 0},
+     {9, 0x02, 0},
+     {23, 0x02, 0},
+     {40, 0x03, 0},
+     {2, 0x02, 36},
+     {9, 0x02, 36},
+     {23, 0x02, 36},
+     {40, 0x03, 36},
+     {2, 0x02, 64},
+     {9, 0x02, 64},
+     {23, 0x02, 64},
+     {40, 0x03, 64},
+     {2, 0x02, 91},
+     {9, 0x02, 91},
+     {23, 0x02, 91},
+     {40, 0x03, 91},
+    },
+    /* 86 */
+    {
+     {3, 0x02, 0},
+     {6, 0x02, 0},
+     {10, 0x02, 0},
+     {15, 0x02, 0},
+     {24, 0x02, 0},
+     {31, 0x02, 0},
+     {41, 0x02, 0},
+     {56, 0x03, 0},
+     {3, 0x02, 36},
+     {6, 0x02, 36},
+     {10, 0x02, 36},
+     {15, 0x02, 36},
+     {24, 0x02, 36},
+     {31, 0x02, 36},
+     {41, 0x02, 36},
+     {56, 0x03, 36},
+    },
+    /* 87 */
+    {
+     {3, 0x02, 64},
+     {6, 0x02, 64},
+     {10, 0x02, 64},
+     {15, 0x02, 64},
+     {24, 0x02, 64},
+     {31, 0x02, 64},
+     {41, 0x02, 64},
+     {56, 0x03, 64},
+     {3, 0x02, 91},
+     {6, 0x02, 91},
+     {10, 0x02, 91},
+     {15, 0x02, 91},
+     {24, 0x02, 91},
+     {31, 0x02, 91},
+     {41, 0x02, 91},
+     {56, 0x03, 91},
+    },
+    /* 88 */
+    {
+     {2, 0x02, 93},
+     {9, 0x02, 93},
+     {23, 0x02, 93},
+     {40, 0x03, 93},
+     {2, 0x02, 126},
+     {9, 0x02, 126},
+     {23, 0x02, 126},
+     {40, 0x03, 126},
+     {1, 0x02, 94},
+     {22, 0x03, 94},
+     {1, 0x02, 125},
+     {22, 0x03, 125},
+     {0, 0x03, 60},
+     {0, 0x03, 96},
+     {0, 0x03, 123},
+     {95, 0x00, 0},
+    },
+    /* 89 */
+    {
+     {3, 0x02, 93},
+     {6, 0x02, 93},
+     {10, 0x02, 93},
+     {15, 0x02, 93},
+     {24, 0x02, 93},
+     {31, 0x02, 93},
+     {41, 0x02, 93},
+     {56, 0x03, 93},
+     {3, 0x02, 126},
+     {6, 0x02, 126},
+     {10, 0x02, 126},
+     {15, 0x02, 126},
+     {24, 0x02, 126},
+     {31, 0x02, 126},
+     {41, 0x02, 126},
+     {56, 0x03, 126},
+    },
+    /* 90 */
+    {
+     {2, 0x02, 94},
+     {9, 0x02, 94},
+     {23, 0x02, 94},
+     {40, 0x03, 94},
+     {2, 0x02, 125},
+     {9, 0x02, 125},
+     {23, 0x02, 125},
+     {40, 0x03, 125},
+     {1, 0x02, 60},
+     {22, 0x03, 60},
+     {1, 0x02, 96},
+     {22, 0x03, 96},
+     {1, 0x02, 123},
+     {22, 0x03, 123},
+     {96, 0x00, 0},
+     {110, 0x00, 0},
+    },
+    /* 91 */
+    {
+     {3, 0x02, 94},
+     {6, 0x02, 94},
+     {10, 0x02, 94},
+     {15, 0x02, 94},
+     {24, 0x02, 94},
+     {31, 0x02, 94},
+     {41, 0x02, 94},
+     {56, 0x03, 94},
+     {3, 0x02, 125},
+     {6, 0x02, 125},
+     {10, 0x02, 125},
+     {15, 0x02, 125},
+     {24, 0x02, 125},
+     {31, 0x02, 125},
+     {41, 0x02, 125},
+     {56, 0x03, 125},
+    },
+    /* 92 */
+    {
+     {2, 0x02, 60},
+     {9, 0x02, 60},
+     {23, 0x02, 60},
+     {40, 0x03, 60},
+     {2, 0x02, 96},
+     {9, 0x02, 96},
+     {23, 0x02, 96},
+     {40, 0x03, 96},
+     {2, 0x02, 123},
+     {9, 0x02, 123},
+     {23, 0x02, 123},
+     {40, 0x03, 123},
+     {97, 0x00, 0},
+     {101, 0x00, 0},
+     {111, 0x00, 0},
+     {133, 0x00, 0},
+    },
+    /* 93 */
+    {
+     {3, 0x02, 60},
+     {6, 0x02, 60},
+     {10, 0x02, 60},
+     {15, 0x02, 60},
+     {24, 0x02, 60},
+     {31, 0x02, 60},
+     {41, 0x02, 60},
+     {56, 0x03, 60},
+     {3, 0x02, 96},
+     {6, 0x02, 96},
+     {10, 0x02, 96},
+     {15, 0x02, 96},
+     {24, 0x02, 96},
+     {31, 0x02, 96},
+     {41, 0x02, 96},
+     {56, 0x03, 96},
+    },
+    /* 94 */
+    {
+     {3, 0x02, 123},
+     {6, 0x02, 123},
+     {10, 0x02, 123},
+     {15, 0x02, 123},
+     {24, 0x02, 123},
+     {31, 0x02, 123},
+     {41, 0x02, 123},
+     {56, 0x03, 123},
+     {98, 0x00, 0},
+     {99, 0x00, 0},
+     {102, 0x00, 0},
+     {105, 0x00, 0},
+     {112, 0x00, 0},
+     {119, 0x00, 0},
+     {134, 0x00, 0},
+     {153, 0x00, 0},
+    },
+    /* 95 */
+    {
+     {0, 0x03, 92},
+     {0, 0x03, 195},
+     {0, 0x03, 208},
+     {100, 0x00, 0},
+     {103, 0x00, 0},
+     {104, 0x00, 0},
+     {106, 0x00, 0},
+     {107, 0x00, 0},
+     {113, 0x00, 0},
+     {116, 0x00, 0},
+     {120, 0x00, 0},
+     {126, 0x00, 0},
+     {135, 0x00, 0},
+     {142, 0x00, 0},
+     {154, 0x00, 0},
+     {169, 0x00, 0},
+    },
+    /* 96 */
+    {
+     {1, 0x02, 92},
+     {22, 0x03, 92},
+     {1, 0x02, 195},
+     {22, 0x03, 195},
+     {1, 0x02, 208},
+     {22, 0x03, 208},
+     {0, 0x03, 128},
+     {0, 0x03, 130},
+     {0, 0x03, 131},
+     {0, 0x03, 162},
+     {0, 0x03, 184},
+     {0, 0x03, 194},
+     {0, 0x03, 224},
+     {0, 0x03, 226},
+     {108, 0x00, 0},
+     {109, 0x00, 0},
+    },
+    /* 97 */
+    {
+     {2, 0x02, 92},
+     {9, 0x02, 92},
+     {23, 0x02, 92},
+     {40, 0x03, 92},
+     {2, 0x02, 195},
+     {9, 0x02, 195},
+     {23, 0x02, 195},
+     {40, 0x03, 195},
+     {2, 0x02, 208},
+     {9, 0x02, 208},
+     {23, 0x02, 208},
+     {40, 0x03, 208},
+     {1, 0x02, 128},
+     {22, 0x03, 128},
+     {1, 0x02, 130},
+     {22, 0x03, 130},
+    },
+    /* 98 */
+    {
+     {3, 0x02, 92},
+     {6, 0x02, 92},
+     {10, 0x02, 92},
+     {15, 0x02, 92},
+     {24, 0x02, 92},
+     {31, 0x02, 92},
+     {41, 0x02, 92},
+     {56, 0x03, 92},
+     {3, 0x02, 195},
+     {6, 0x02, 195},
+     {10, 0x02, 195},
+     {15, 0x02, 195},
+     {24, 0x02, 195},
+     {31, 0x02, 195},
+     {41, 0x02, 195},
+     {56, 0x03, 195},
+    },
+    /* 99 */
+    {
+     {3, 0x02, 208},
+     {6, 0x02, 208},
+     {10, 0x02, 208},
+     {15, 0x02, 208},
+     {24, 0x02, 208},
+     {31, 0x02, 208},
+     {41, 0x02, 208},
+     {56, 0x03, 208},
+     {2, 0x02, 128},
+     {9, 0x02, 128},
+     {23, 0x02, 128},
+     {40, 0x03, 128},
+     {2, 0x02, 130},
+     {9, 0x02, 130},
+     {23, 0x02, 130},
+     {40, 0x03, 130},
+    },
+    /* 100 */
+    {
+     {3, 0x02, 128},
+     {6, 0x02, 128},
+     {10, 0x02, 128},
+     {15, 0x02, 128},
+     {24, 0x02, 128},
+     {31, 0x02, 128},
+     {41, 0x02, 128},
+     {56, 0x03, 128},
+     {3, 0x02, 130},
+     {6, 0x02, 130},
+     {10, 0x02, 130},
+     {15, 0x02, 130},
+     {24, 0x02, 130},
+     {31, 0x02, 130},
+     {41, 0x02, 130},
+     {56, 0x03, 130},
+    },
+    /* 101 */
+    {
+     {1, 0x02, 131},
+     {22, 0x03, 131},
+     {1, 0x02, 162},
+     {22, 0x03, 162},
+     {1, 0x02, 184},
+     {22, 0x03, 184},
+     {1, 0x02, 194},
+     {22, 0x03, 194},
+     {1, 0x02, 224},
+     {22, 0x03, 224},
+     {1, 0x02, 226},
+     {22, 0x03, 226},
+     {0, 0x03, 153},
+     {0, 0x03, 161},
+     {0, 0x03, 167},
+     {0, 0x03, 172},
+    },
+    /* 102 */
+    {
+     {2, 0x02, 131},
+     {9, 0x02, 131},
+     {23, 0x02, 131},
+     {40, 0x03, 131},
+     {2, 0x02, 162},
+     {9, 0x02, 162},
+     {23, 0x02, 162},
+     {40, 0x03, 162},
+     {2, 0x02, 184},
+     {9, 0x02, 184},
+     {23, 0x02, 184},
+     {40, 0x03, 184},
+     {2, 0x02, 194},
+     {9, 0x02, 194},
+     {23, 0x02, 194},
+     {40, 0x03, 194},
+    },
+    /* 103 */
+    {
+     {3, 0x02, 131},
+     {6, 0x02, 131},
+     {10, 0x02, 131},
+     {15, 0x02, 131},
+     {24, 0x02, 131},
+     {31, 0x02, 131},
+     {41, 0x02, 131},
+     {56, 0x03, 131},
+     {3, 0x02, 162},
+     {6, 0x02, 162},
+     {10, 0x02, 162},
+     {15, 0x02, 162},
+     {24, 0x02, 162},
+     {31, 0x02, 162},
+     {41, 0x02, 162},
+     {56, 0x03, 162},
+    },
+    /* 104 */
+    {
+     {3, 0x02, 184},
+     {6, 0x02, 184},
+     {10, 0x02, 184},
+     {15, 0x02, 184},
+     {24, 0x02, 184},
+     {31, 0x02, 184},
+     {41, 0x02, 184},
+     {56, 0x03, 184},
+     {3, 0x02, 194},
+     {6, 0x02, 194},
+     {10, 0x02, 194},
+     {15, 0x02, 194},
+     {24, 0x02, 194},
+     {31, 0x02, 194},
+     {41, 0x02, 194},
+     {56, 0x03, 194},
+    },
+    /* 105 */
+    {
+     {2, 0x02, 224},
+     {9, 0x02, 224},
+     {23, 0x02, 224},
+     {40, 0x03, 224},
+     {2, 0x02, 226},
+     {9, 0x02, 226},
+     {23, 0x02, 226},
+     {40, 0x03, 226},
+     {1, 0x02, 153},
+     {22, 0x03, 153},
+     {1, 0x02, 161},
+     {22, 0x03, 161},
+     {1, 0x02, 167},
+     {22, 0x03, 167},
+     {1, 0x02, 172},
+     {22, 0x03, 172},
+    },
+    /* 106 */
+    {
+     {3, 0x02, 224},
+     {6, 0x02, 224},
+     {10, 0x02, 224},
+     {15, 0x02, 224},
+     {24, 0x02, 224},
+     {31, 0x02, 224},
+     {41, 0x02, 224},
+     {56, 0x03, 224},
+     {3, 0x02, 226},
+     {6, 0x02, 226},
+     {10, 0x02, 226},
+     {15, 0x02, 226},
+     {24, 0x02, 226},
+     {31, 0x02, 226},
+     {41, 0x02, 226},
+     {56, 0x03, 226},
+    },
+    /* 107 */
+    {
+     {2, 0x02, 153},
+     {9, 0x02, 153},
+     {23, 0x02, 153},
+     {40, 0x03, 153},
+     {2, 0x02, 161},
+     {9, 0x02, 161},
+     {23, 0x02, 161},
+     {40, 0x03, 161},
+     {2, 0x02, 167},
+     {9, 0x02, 167},
+     {23, 0x02, 167},
+     {40, 0x03, 167},
+     {2, 0x02, 172},
+     {9, 0x02, 172},
+     {23, 0x02, 172},
+     {40, 0x03, 172},
+    },
+    /* 108 */
+    {
+     {3, 0x02, 153},
+     {6, 0x02, 153},
+     {10, 0x02, 153},
+     {15, 0x02, 153},
+     {24, 0x02, 153},
+     {31, 0x02, 153},
+     {41, 0x02, 153},
+     {56, 0x03, 153},
+     {3, 0x02, 161},
+     {6, 0x02, 161},
+     {10, 0x02, 161},
+     {15, 0x02, 161},
+     {24, 0x02, 161},
+     {31, 0x02, 161},
+     {41, 0x02, 161},
+     {56, 0x03, 161},
+    },
+    /* 109 */
+    {
+     {3, 0x02, 167},
+     {6, 0x02, 167},
+     {10, 0x02, 167},
+     {15, 0x02, 167},
+     {24, 0x02, 167},
+     {31, 0x02, 167},
+     {41, 0x02, 167},
+     {56, 0x03, 167},
+     {3, 0x02, 172},
+     {6, 0x02, 172},
+     {10, 0x02, 172},
+     {15, 0x02, 172},
+     {24, 0x02, 172},
+     {31, 0x02, 172},
+     {41, 0x02, 172},
+     {56, 0x03, 172},
+    },
+    /* 110 */
+    {
+     {114, 0x00, 0},
+     {115, 0x00, 0},
+     {117, 0x00, 0},
+     {118, 0x00, 0},
+     {121, 0x00, 0},
+     {123, 0x00, 0},
+     {127, 0x00, 0},
+     {130, 0x00, 0},
+     {136, 0x00, 0},
+     {139, 0x00, 0},
+     {143, 0x00, 0},
+     {146, 0x00, 0},
+     {155, 0x00, 0},
+     {162, 0x00, 0},
+     {170, 0x00, 0},
+     {180, 0x00, 0},
+    },
+    /* 111 */
+    {
+     {0, 0x03, 176},
+     {0, 0x03, 177},
+     {0, 0x03, 179},
+     {0, 0x03, 209},
+     {0, 0x03, 216},
+     {0, 0x03, 217},
+     {0, 0x03, 227},
+     {0, 0x03, 229},
+     {0, 0x03, 230},
+     {122, 0x00, 0},
+     {124, 0x00, 0},
+     {125, 0x00, 0},
+     {128, 0x00, 0},
+     {129, 0x00, 0},
+     {131, 0x00, 0},
+     {132, 0x00, 0},
+    },
+    /* 112 */
+    {
+     {1, 0x02, 176},
+     {22, 0x03, 176},
+     {1, 0x02, 177},
+     {22, 0x03, 177},
+     {1, 0x02, 179},
+     {22, 0x03, 179},
+     {1, 0x02, 209},
+     {22, 0x03, 209},
+     {1, 0x02, 216},
+     {22, 0x03, 216},
+     {1, 0x02, 217},
+     {22, 0x03, 217},
+     {1, 0x02, 227},
+     {22, 0x03, 227},
+     {1, 0x02, 229},
+     {22, 0x03, 229},
+    },
+    /* 113 */
+    {
+     {2, 0x02, 176},
+     {9, 0x02, 176},
+     {23, 0x02, 176},
+     {40, 0x03, 176},
+     {2, 0x02, 177},
+     {9, 0x02, 177},
+     {23, 0x02, 177},
+     {40, 0x03, 177},
+     {2, 0x02, 179},
+     {9, 0x02, 179},
+     {23, 0x02, 179},
+     {40, 0x03, 179},
+     {2, 0x02, 209},
+     {9, 0x02, 209},
+     {23, 0x02, 209},
+     {40, 0x03, 209},
+    },
+    /* 114 */
+    {
+     {3, 0x02, 176},
+     {6, 0x02, 176},
+     {10, 0x02, 176},
+     {15, 0x02, 176},
+     {24, 0x02, 176},
+     {31, 0x02, 176},
+     {41, 0x02, 176},
+     {56, 0x03, 176},
+     {3, 0x02, 177},
+     {6, 0x02, 177},
+     {10, 0x02, 177},
+     {15, 0x02, 177},
+     {24, 0x02, 177},
+     {31, 0x02, 177},
+     {41, 0x02, 177},
+     {56, 0x03, 177},
+    },
+    /* 115 */
+    {
+     {3, 0x02, 179},
+     {6, 0x02, 179},
+     {10, 0x02, 179},
+     {15, 0x02, 179},
+     {24, 0x02, 179},
+     {31, 0x02, 179},
+     {41, 0x02, 179},
+     {56, 0x03, 179},
+     {3, 0x02, 209},
+     {6, 0x02, 209},
+     {10, 0x02, 209},
+     {15, 0x02, 209},
+     {24, 0x02, 209},
+     {31, 0x02, 209},
+     {41, 0x02, 209},
+     {56, 0x03, 209},
+    },
+    /* 116 */
+    {
+     {2, 0x02, 216},
+     {9, 0x02, 216},
+     {23, 0x02, 216},
+     {40, 0x03, 216},
+     {2, 0x02, 217},
+     {9, 0x02, 217},
+     {23, 0x02, 217},
+     {40, 0x03, 217},
+     {2, 0x02, 227},
+     {9, 0x02, 227},
+     {23, 0x02, 227},
+     {40, 0x03, 227},
+     {2, 0x02, 229},
+     {9, 0x02, 229},
+     {23, 0x02, 229},
+     {40, 0x03, 229},
+    },
+    /* 117 */
+    {
+     {3, 0x02, 216},
+     {6, 0x02, 216},
+     {10, 0x02, 216},
+     {15, 0x02, 216},
+     {24, 0x02, 216},
+     {31, 0x02, 216},
+     {41, 0x02, 216},
+     {56, 0x03, 216},
+     {3, 0x02, 217},
+     {6, 0x02, 217},
+     {10, 0x02, 217},
+     {15, 0x02, 217},
+     {24, 0x02, 217},
+     {31, 0x02, 217},
+     {41, 0x02, 217},
+     {56, 0x03, 217},
+    },
+    /* 118 */
+    {
+     {3, 0x02, 227},
+     {6, 0x02, 227},
+     {10, 0x02, 227},
+     {15, 0x02, 227},
+     {24, 0x02, 227},
+     {31, 0x02, 227},
+     {41, 0x02, 227},
+     {56, 0x03, 227},
+     {3, 0x02, 229},
+     {6, 0x02, 229},
+     {10, 0x02, 229},
+     {15, 0x02, 229},
+     {24, 0x02, 229},
+     {31, 0x02, 229},
+     {41, 0x02, 229},
+     {56, 0x03, 229},
+    },
+    /* 119 */
+    {
+     {1, 0x02, 230},
+     {22, 0x03, 230},
+     {0, 0x03, 129},
+     {0, 0x03, 132},
+     {0, 0x03, 133},
+     {0, 0x03, 134},
+     {0, 0x03, 136},
+     {0, 0x03, 146},
+     {0, 0x03, 154},
+     {0, 0x03, 156},
+     {0, 0x03, 160},
+     {0, 0x03, 163},
+     {0, 0x03, 164},
+     {0, 0x03, 169},
+     {0, 0x03, 170},
+     {0, 0x03, 173},
+    },
+    /* 120 */
+    {
+     {2, 0x02, 230},
+     {9, 0x02, 230},
+     {23, 0x02, 230},
+     {40, 0x03, 230},
+     {1, 0x02, 129},
+     {22, 0x03, 129},
+     {1, 0x02, 132},
+     {22, 0x03, 132},
+     {1, 0x02, 133},
+     {22, 0x03, 133},
+     {1, 0x02, 134},
+     {22, 0x03, 134},
+     {1, 0x02, 136},
+     {22, 0x03, 136},
+     {1, 0x02, 146},
+     {22, 0x03, 146},
+    },
+    /* 121 */
+    {
+     {3, 0x02, 230},
+     {6, 0x02, 230},
+     {10, 0x02, 230},
+     {15, 0x02, 230},
+     {24, 0x02, 230},
+     {31, 0x02, 230},
+     {41, 0x02, 230},
+     {56, 0x03, 230},
+     {2, 0x02, 129},
+     {9, 0x02, 129},
+     {23, 0x02, 129},
+     {40, 0x03, 129},
+     {2, 0x02, 132},
+     {9, 0x02, 132},
+     {23, 0x02, 132},
+     {40, 0x03, 132},
+    },
+    /* 122 */
+    {
+     {3, 0x02, 129},
+     {6, 0x02, 129},
+     {10, 0x02, 129},
+     {15, 0x02, 129},
+     {24, 0x02, 129},
+     {31, 0x02, 129},
+     {41, 0x02, 129},
+     {56, 0x03, 129},
+     {3, 0x02, 132},
+     {6, 0x02, 132},
+     {10, 0x02, 132},
+     {15, 0x02, 132},
+     {24, 0x02, 132},
+     {31, 0x02, 132},
+     {41, 0x02, 132},
+     {56, 0x03, 132},
+    },
+    /* 123 */
+    {
+     {2, 0x02, 133},
+     {9, 0x02, 133},
+     {23, 0x02, 133},
+     {40, 0x03, 133},
+     {2, 0x02, 134},
+     {9, 0x02, 134},
+     {23, 0x02, 134},
+     {40, 0x03, 134},
+     {2, 0x02, 136},
+     {9, 0x02, 136},
+     {23, 0x02, 136},
+     {40, 0x03, 136},
+     {2, 0x02, 146},
+     {9, 0x02, 146},
+     {23, 0x02, 146},
+     {40, 0x03, 146},
+    },
+    /* 124 */
+    {
+     {3, 0x02, 133},
+     {6, 0x02, 133},
+     {10, 0x02, 133},
+     {15, 0x02, 133},
+     {24, 0x02, 133},
+     {31, 0x02, 133},
+     {41, 0x02, 133},
+     {56, 0x03, 133},
+     {3, 0x02, 134},
+     {6, 0x02, 134},
+     {10, 0x02, 134},
+     {15, 0x02, 134},
+     {24, 0x02, 134},
+     {31, 0x02, 134},
+     {41, 0x02, 134},
+     {56, 0x03, 134},
+    },
+    /* 125 */
+    {
+     {3, 0x02, 136},
+     {6, 0x02, 136},
+     {10, 0x02, 136},
+     {15, 0x02, 136},
+     {24, 0x02, 136},
+     {31, 0x02, 136},
+     {41, 0x02, 136},
+     {56, 0x03, 136},
+     {3, 0x02, 146},
+     {6, 0x02, 146},
+     {10, 0x02, 146},
+     {15, 0x02, 146},
+     {24, 0x02, 146},
+     {31, 0x02, 146},
+     {41, 0x02, 146},
+     {56, 0x03, 146},
+    },
+    /* 126 */
+    {
+     {1, 0x02, 154},
+     {22, 0x03, 154},
+     {1, 0x02, 156},
+     {22, 0x03, 156},
+     {1, 0x02, 160},
+     {22, 0x03, 160},
+     {1, 0x02, 163},
+     {22, 0x03, 163},
+     {1, 0x02, 164},
+     {22, 0x03, 164},
+     {1, 0x02, 169},
+     {22, 0x03, 169},
+     {1, 0x02, 170},
+     {22, 0x03, 170},
+     {1, 0x02, 173},
+     {22, 0x03, 173},
+    },
+    /* 127 */
+    {
+     {2, 0x02, 154},
+     {9, 0x02, 154},
+     {23, 0x02, 154},
+     {40, 0x03, 154},
+     {2, 0x02, 156},
+     {9, 0x02, 156},
+     {23, 0x02, 156},
+     {40, 0x03, 156},
+     {2, 0x02, 160},
+     {9, 0x02, 160},
+     {23, 0x02, 160},
+     {40, 0x03, 160},
+     {2, 0x02, 163},
+     {9, 0x02, 163},
+     {23, 0x02, 163},
+     {40, 0x03, 163},
+    },
+    /* 128 */
+    {
+     {3, 0x02, 154},
+     {6, 0x02, 154},
+     {10, 0x02, 154},
+     {15, 0x02, 154},
+     {24, 0x02, 154},
+     {31, 0x02, 154},
+     {41, 0x02, 154},
+     {56, 0x03, 154},
+     {3, 0x02, 156},
+     {6, 0x02, 156},
+     {10, 0x02, 156},
+     {15, 0x02, 156},
+     {24, 0x02, 156},
+     {31, 0x02, 156},
+     {41, 0x02, 156},
+     {56, 0x03, 156},
+    },
+    /* 129 */
+    {
+     {3, 0x02, 160},
+     {6, 0x02, 160},
+     {10, 0x02, 160},
+     {15, 0x02, 160},
+     {24, 0x02, 160},
+     {31, 0x02, 160},
+     {41, 0x02, 160},
+     {56, 0x03, 160},
+     {3, 0x02, 163},
+     {6, 0x02, 163},
+     {10, 0x02, 163},
+     {15, 0x02, 163},
+     {24, 0x02, 163},
+     {31, 0x02, 163},
+     {41, 0x02, 163},
+     {56, 0x03, 163},
+    },
+    /* 130 */
+    {
+     {2, 0x02, 164},
+     {9, 0x02, 164},
+     {23, 0x02, 164},
+     {40, 0x03, 164},
+     {2, 0x02, 169},
+     {9, 0x02, 169},
+     {23, 0x02, 169},
+     {40, 0x03, 169},
+     {2, 0x02, 170},
+     {9, 0x02, 170},
+     {23, 0x02, 170},
+     {40, 0x03, 170},
+     {2, 0x02, 173},
+     {9, 0x02, 173},
+     {23, 0x02, 173},
+     {40, 0x03, 173},
+    },
+    /* 131 */
+    {
+     {3, 0x02, 164},
+     {6, 0x02, 164},
+     {10, 0x02, 164},
+     {15, 0x02, 164},
+     {24, 0x02, 164},
+     {31, 0x02, 164},
+     {41, 0x02, 164},
+     {56, 0x03, 164},
+     {3, 0x02, 169},
+     {6, 0x02, 169},
+     {10, 0x02, 169},
+     {15, 0x02, 169},
+     {24, 0x02, 169},
+     {31, 0x02, 169},
+     {41, 0x02, 169},
+     {56, 0x03, 169},
+    },
+    /* 132 */
+    {
+     {3, 0x02, 170},
+     {6, 0x02, 170},
+     {10, 0x02, 170},
+     {15, 0x02, 170},
+     {24, 0x02, 170},
+     {31, 0x02, 170},
+     {41, 0x02, 170},
+     {56, 0x03, 170},
+     {3, 0x02, 173},
+     {6, 0x02, 173},
+     {10, 0x02, 173},
+     {15, 0x02, 173},
+     {24, 0x02, 173},
+     {31, 0x02, 173},
+     {41, 0x02, 173},
+     {56, 0x03, 173},
+    },
+    /* 133 */
+    {
+     {137, 0x00, 0},
+     {138, 0x00, 0},
+     {140, 0x00, 0},
+     {141, 0x00, 0},
+     {144, 0x00, 0},
+     {145, 0x00, 0},
+     {147, 0x00, 0},
+     {150, 0x00, 0},
+     {156, 0x00, 0},
+     {159, 0x00, 0},
+     {163, 0x00, 0},
+     {166, 0x00, 0},
+     {171, 0x00, 0},
+     {174, 0x00, 0},
+     {181, 0x00, 0},
+     {190, 0x00, 0},
+    },
+    /* 134 */
+    {
+     {0, 0x03, 178},
+     {0, 0x03, 181},
+     {0, 0x03, 185},
+     {0, 0x03, 186},
+     {0, 0x03, 187},
+     {0, 0x03, 189},
+     {0, 0x03, 190},
+     {0, 0x03, 196},
+     {0, 0x03, 198},
+     {0, 0x03, 228},
+     {0, 0x03, 232},
+     {0, 0x03, 233},
+     {148, 0x00, 0},
+     {149, 0x00, 0},
+     {151, 0x00, 0},
+     {152, 0x00, 0},
+    },
+    /* 135 */
+    {
+     {1, 0x02, 178},
+     {22, 0x03, 178},
+     {1, 0x02, 181},
+     {22, 0x03, 181},
+     {1, 0x02, 185},
+     {22, 0x03, 185},
+     {1, 0x02, 186},
+     {22, 0x03, 186},
+     {1, 0x02, 187},
+     {22, 0x03, 187},
+     {1, 0x02, 189},
+     {22, 0x03, 189},
+     {1, 0x02, 190},
+     {22, 0x03, 190},
+     {1, 0x02, 196},
+     {22, 0x03, 196},
+    },
+    /* 136 */
+    {
+     {2, 0x02, 178},
+     {9, 0x02, 178},
+     {23, 0x02, 178},
+     {40, 0x03, 178},
+     {2, 0x02, 181},
+     {9, 0x02, 181},
+     {23, 0x02, 181},
+     {40, 0x03, 181},
+     {2, 0x02, 185},
+     {9, 0x02, 185},
+     {23, 0x02, 185},
+     {40, 0x03, 185},
+     {2, 0x02, 186},
+     {9, 0x02, 186},
+     {23, 0x02, 186},
+     {40, 0x03, 186},
+    },
+    /* 137 */
+    {
+     {3, 0x02, 178},
+     {6, 0x02, 178},
+     {10, 0x02, 178},
+     {15, 0x02, 178},
+     {24, 0x02, 178},
+     {31, 0x02, 178},
+     {41, 0x02, 178},
+     {56, 0x03, 178},
+     {3, 0x02, 181},
+     {6, 0x02, 181},
+     {10, 0x02, 181},
+     {15, 0x02, 181},
+     {24, 0x02, 181},
+     {31, 0x02, 181},
+     {41, 0x02, 181},
+     {56, 0x03, 181},
+    },
+    /* 138 */
+    {
+     {3, 0x02, 185},
+     {6, 0x02, 185},
+     {10, 0x02, 185},
+     {15, 0x02, 185},
+     {24, 0x02, 185},
+     {31, 0x02, 185},
+     {41, 0x02, 185},
+     {56, 0x03, 185},
+     {3, 0x02, 186},
+     {6, 0x02, 186},
+     {10, 0x02, 186},
+     {15, 0x02, 186},
+     {24, 0x02, 186},
+     {31, 0x02, 186},
+     {41, 0x02, 186},
+     {56, 0x03, 186},
+    },
+    /* 139 */
+    {
+     {2, 0x02, 187},
+     {9, 0x02, 187},
+     {23, 0x02, 187},
+     {40, 0x03, 187},
+     {2, 0x02, 189},
+     {9, 0x02, 189},
+     {23, 0x02, 189},
+     {40, 0x03, 189},
+     {2, 0x02, 190},
+     {9, 0x02, 190},
+     {23, 0x02, 190},
+     {40, 0x03, 190},
+     {2, 0x02, 196},
+     {9, 0x02, 196},
+     {23, 0x02, 196},
+     {40, 0x03, 196},
+    },
+    /* 140 */
+    {
+     {3, 0x02, 187},
+     {6, 0x02, 187},
+     {10, 0x02, 187},
+     {15, 0x02, 187},
+     {24, 0x02, 187},
+     {31, 0x02, 187},
+     {41, 0x02, 187},
+     {56, 0x03, 187},
+     {3, 0x02, 189},
+     {6, 0x02, 189},
+     {10, 0x02, 189},
+     {15, 0x02, 189},
+     {24, 0x02, 189},
+     {31, 0x02, 189},
+     {41, 0x02, 189},
+     {56, 0x03, 189},
+    },
+    /* 141 */
+    {
+     {3, 0x02, 190},
+     {6, 0x02, 190},
+     {10, 0x02, 190},
+     {15, 0x02, 190},
+     {24, 0x02, 190},
+     {31, 0x02, 190},
+     {41, 0x02, 190},
+     {56, 0x03, 190},
+     {3, 0x02, 196},
+     {6, 0x02, 196},
+     {10, 0x02, 196},
+     {15, 0x02, 196},
+     {24, 0x02, 196},
+     {31, 0x02, 196},
+     {41, 0x02, 196},
+     {56, 0x03, 196},
+    },
+    /* 142 */
+    {
+     {1, 0x02, 198},
+     {22, 0x03, 198},
+     {1, 0x02, 228},
+     {22, 0x03, 228},
+     {1, 0x02, 232},
+     {22, 0x03, 232},
+     {1, 0x02, 233},
+     {22, 0x03, 233},
+     {0, 0x03, 1},
+     {0, 0x03, 135},
+     {0, 0x03, 137},
+     {0, 0x03, 138},
+     {0, 0x03, 139},
+     {0, 0x03, 140},
+     {0, 0x03, 141},
+     {0, 0x03, 143},
+    },
+    /* 143 */
+    {
+     {2, 0x02, 198},
+     {9, 0x02, 198},
+     {23, 0x02, 198},
+     {40, 0x03, 198},
+     {2, 0x02, 228},
+     {9, 0x02, 228},
+     {23, 0x02, 228},
+     {40, 0x03, 228},
+     {2, 0x02, 232},
+     {9, 0x02, 232},
+     {23, 0x02, 232},
+     {40, 0x03, 232},
+     {2, 0x02, 233},
+     {9, 0x02, 233},
+     {23, 0x02, 233},
+     {40, 0x03, 233},
+    },
+    /* 144 */
+    {
+     {3, 0x02, 198},
+     {6, 0x02, 198},
+     {10, 0x02, 198},
+     {15, 0x02, 198},
+     {24, 0x02, 198},
+     {31, 0x02, 198},
+     {41, 0x02, 198},
+     {56, 0x03, 198},
+     {3, 0x02, 228},
+     {6, 0x02, 228},
+     {10, 0x02, 228},
+     {15, 0x02, 228},
+     {24, 0x02, 228},
+     {31, 0x02, 228},
+     {41, 0x02, 228},
+     {56, 0x03, 228},
+    },
+    /* 145 */
+    {
+     {3, 0x02, 232},
+     {6, 0x02, 232},
+     {10, 0x02, 232},
+     {15, 0x02, 232},
+     {24, 0x02, 232},
+     {31, 0x02, 232},
+     {41, 0x02, 232},
+     {56, 0x03, 232},
+     {3, 0x02, 233},
+     {6, 0x02, 233},
+     {10, 0x02, 233},
+     {15, 0x02, 233},
+     {24, 0x02, 233},
+     {31, 0x02, 233},
+     {41, 0x02, 233},
+     {56, 0x03, 233},
+    },
+    /* 146 */
+    {
+     {1, 0x02, 1},
+     {22, 0x03, 1},
+     {1, 0x02, 135},
+     {22, 0x03, 135},
+     {1, 0x02, 137},
+     {22, 0x03, 137},
+     {1, 0x02, 138},
+     {22, 0x03, 138},
+     {1, 0x02, 139},
+     {22, 0x03, 139},
+     {1, 0x02, 140},
+     {22, 0x03, 140},
+     {1, 0x02, 141},
+     {22, 0x03, 141},
+     {1, 0x02, 143},
+     {22, 0x03, 143},
+    },
+    /* 147 */
+    {
+     {2, 0x02, 1},
+     {9, 0x02, 1},
+     {23, 0x02, 1},
+     {40, 0x03, 1},
+     {2, 0x02, 135},
+     {9, 0x02, 135},
+     {23, 0x02, 135},
+     {40, 0x03, 135},
+     {2, 0x02, 137},
+     {9, 0x02, 137},
+     {23, 0x02, 137},
+     {40, 0x03, 137},
+     {2, 0x02, 138},
+     {9, 0x02, 138},
+     {23, 0x02, 138},
+     {40, 0x03, 138},
+    },
+    /* 148 */
+    {
+     {3, 0x02, 1},
+     {6, 0x02, 1},
+     {10, 0x02, 1},
+     {15, 0x02, 1},
+     {24, 0x02, 1},
+     {31, 0x02, 1},
+     {41, 0x02, 1},
+     {56, 0x03, 1},
+     {3, 0x02, 135},
+     {6, 0x02, 135},
+     {10, 0x02, 135},
+     {15, 0x02, 135},
+     {24, 0x02, 135},
+     {31, 0x02, 135},
+     {41, 0x02, 135},
+     {56, 0x03, 135},
+    },
+    /* 149 */
+    {
+     {3, 0x02, 137},
+     {6, 0x02, 137},
+     {10, 0x02, 137},
+     {15, 0x02, 137},
+     {24, 0x02, 137},
+     {31, 0x02, 137},
+     {41, 0x02, 137},
+     {56, 0x03, 137},
+     {3, 0x02, 138},
+     {6, 0x02, 138},
+     {10, 0x02, 138},
+     {15, 0x02, 138},
+     {24, 0x02, 138},
+     {31, 0x02, 138},
+     {41, 0x02, 138},
+     {56, 0x03, 138},
+    },
+    /* 150 */
+    {
+     {2, 0x02, 139},
+     {9, 0x02, 139},
+     {23, 0x02, 139},
+     {40, 0x03, 139},
+     {2, 0x02, 140},
+     {9, 0x02, 140},
+     {23, 0x02, 140},
+     {40, 0x03, 140},
+     {2, 0x02, 141},
+     {9, 0x02, 141},
+     {23, 0x02, 141},
+     {40, 0x03, 141},
+     {2, 0x02, 143},
+     {9, 0x02, 143},
+     {23, 0x02, 143},
+     {40, 0x03, 143},
+    },
+    /* 151 */
+    {
+     {3, 0x02, 139},
+     {6, 0x02, 139},
+     {10, 0x02, 139},
+     {15, 0x02, 139},
+     {24, 0x02, 139},
+     {31, 0x02, 139},
+     {41, 0x02, 139},
+     {56, 0x03, 139},
+     {3, 0x02, 140},
+     {6, 0x02, 140},
+     {10, 0x02, 140},
+     {15, 0x02, 140},
+     {24, 0x02, 140},
+     {31, 0x02, 140},
+     {41, 0x02, 140},
+     {56, 0x03, 140},
+    },
+    /* 152 */
+    {
+     {3, 0x02, 141},
+     {6, 0x02, 141},
+     {10, 0x02, 141},
+     {15, 0x02, 141},
+     {24, 0x02, 141},
+     {31, 0x02, 141},
+     {41, 0x02, 141},
+     {56, 0x03, 141},
+     {3, 0x02, 143},
+     {6, 0x02, 143},
+     {10, 0x02, 143},
+     {15, 0x02, 143},
+     {24, 0x02, 143},
+     {31, 0x02, 143},
+     {41, 0x02, 143},
+     {56, 0x03, 143},
+    },
+    /* 153 */
+    {
+     {157, 0x00, 0},
+     {158, 0x00, 0},
+     {160, 0x00, 0},
+     {161, 0x00, 0},
+     {164, 0x00, 0},
+     {165, 0x00, 0},
+     {167, 0x00, 0},
+     {168, 0x00, 0},
+     {172, 0x00, 0},
+     {173, 0x00, 0},
+     {175, 0x00, 0},
+     {177, 0x00, 0},
+     {182, 0x00, 0},
+     {185, 0x00, 0},
+     {191, 0x00, 0},
+     {207, 0x00, 0},
+    },
+    /* 154 */
+    {
+     {0, 0x03, 147},
+     {0, 0x03, 149},
+     {0, 0x03, 150},
+     {0, 0x03, 151},
+     {0, 0x03, 152},
+     {0, 0x03, 155},
+     {0, 0x03, 157},
+     {0, 0x03, 158},
+     {0, 0x03, 165},
+     {0, 0x03, 166},
+     {0, 0x03, 168},
+     {0, 0x03, 174},
+     {0, 0x03, 175},
+     {0, 0x03, 180},
+     {0, 0x03, 182},
+     {0, 0x03, 183},
+    },
+    /* 155 */
+    {
+     {1, 0x02, 147},
+     {22, 0x03, 147},
+     {1, 0x02, 149},
+     {22, 0x03, 149},
+     {1, 0x02, 150},
+     {22, 0x03, 150},
+     {1, 0x02, 151},
+     {22, 0x03, 151},
+     {1, 0x02, 152},
+     {22, 0x03, 152},
+     {1, 0x02, 155},
+     {22, 0x03, 155},
+     {1, 0x02, 157},
+     {22, 0x03, 157},
+     {1, 0x02, 158},
+     {22, 0x03, 158},
+    },
+    /* 156 */
+    {
+     {2, 0x02, 147},
+     {9, 0x02, 147},
+     {23, 0x02, 147},
+     {40, 0x03, 147},
+     {2, 0x02, 149},
+     {9, 0x02, 149},
+     {23, 0x02, 149},
+     {40, 0x03, 149},
+     {2, 0x02, 150},
+     {9, 0x02, 150},
+     {23, 0x02, 150},
+     {40, 0x03, 150},
+     {2, 0x02, 151},
+     {9, 0x02, 151},
+     {23, 0x02, 151},
+     {40, 0x03, 151},
+    },
+    /* 157 */
+    {
+     {3, 0x02, 147},
+     {6, 0x02, 147},
+     {10, 0x02, 147},
+     {15, 0x02, 147},
+     {24, 0x02, 147},
+     {31, 0x02, 147},
+     {41, 0x02, 147},
+     {56, 0x03, 147},
+     {3, 0x02, 149},
+     {6, 0x02, 149},
+     {10, 0x02, 149},
+     {15, 0x02, 149},
+     {24, 0x02, 149},
+     {31, 0x02, 149},
+     {41, 0x02, 149},
+     {56, 0x03, 149},
+    },
+    /* 158 */
+    {
+     {3, 0x02, 150},
+     {6, 0x02, 150},
+     {10, 0x02, 150},
+     {15, 0x02, 150},
+     {24, 0x02, 150},
+     {31, 0x02, 150},
+     {41, 0x02, 150},
+     {56, 0x03, 150},
+     {3, 0x02, 151},
+     {6, 0x02, 151},
+     {10, 0x02, 151},
+     {15, 0x02, 151},
+     {24, 0x02, 151},
+     {31, 0x02, 151},
+     {41, 0x02, 151},
+     {56, 0x03, 151},
+    },
+    /* 159 */
+    {
+     {2, 0x02, 152},
+     {9, 0x02, 152},
+     {23, 0x02, 152},
+     {40, 0x03, 152},
+     {2, 0x02, 155},
+     {9, 0x02, 155},
+     {23, 0x02, 155},
+     {40, 0x03, 155},
+     {2, 0x02, 157},
+     {9, 0x02, 157},
+     {23, 0x02, 157},
+     {40, 0x03, 157},
+     {2, 0x02, 158},
+     {9, 0x02, 158},
+     {23, 0x02, 158},
+     {40, 0x03, 158},
+    },
+    /* 160 */
+    {
+     {3, 0x02, 152},
+     {6, 0x02, 152},
+     {10, 0x02, 152},
+     {15, 0x02, 152},
+     {24, 0x02, 152},
+     {31, 0x02, 152},
+     {41, 0x02, 152},
+     {56, 0x03, 152},
+     {3, 0x02, 155},
+     {6, 0x02, 155},
+     {10, 0x02, 155},
+     {15, 0x02, 155},
+     {24, 0x02, 155},
+     {31, 0x02, 155},
+     {41, 0x02, 155},
+     {56, 0x03, 155},
+    },
+    /* 161 */
+    {
+     {3, 0x02, 157},
+     {6, 0x02, 157},
+     {10, 0x02, 157},
+     {15, 0x02, 157},
+     {24, 0x02, 157},
+     {31, 0x02, 157},
+     {41, 0x02, 157},
+     {56, 0x03, 157},
+     {3, 0x02, 158},
+     {6, 0x02, 158},
+     {10, 0x02, 158},
+     {15, 0x02, 158},
+     {24, 0x02, 158},
+     {31, 0x02, 158},
+     {41, 0x02, 158},
+     {56, 0x03, 158},
+    },
+    /* 162 */
+    {
+     {1, 0x02, 165},
+     {22, 0x03, 165},
+     {1, 0x02, 166},
+     {22, 0x03, 166},
+     {1, 0x02, 168},
+     {22, 0x03, 168},
+     {1, 0x02, 174},
+     {22, 0x03, 174},
+     {1, 0x02, 175},
+     {22, 0x03, 175},
+     {1, 0x02, 180},
+     {22, 0x03, 180},
+     {1, 0x02, 182},
+     {22, 0x03, 182},
+     {1, 0x02, 183},
+     {22, 0x03, 183},
+    },
+    /* 163 */
+    {
+     {2, 0x02, 165},
+     {9, 0x02, 165},
+     {23, 0x02, 165},
+     {40, 0x03, 165},
+     {2, 0x02, 166},
+     {9, 0x02, 166},
+     {23, 0x02, 166},
+     {40, 0x03, 166},
+     {2, 0x02, 168},
+     {9, 0x02, 168},
+     {23, 0x02, 168},
+     {40, 0x03, 168},
+     {2, 0x02, 174},
+     {9, 0x02, 174},
+     {23, 0x02, 174},
+     {40, 0x03, 174},
+    },
+    /* 164 */
+    {
+     {3, 0x02, 165},
+     {6, 0x02, 165},
+     {10, 0x02, 165},
+     {15, 0x02, 165},
+     {24, 0x02, 165},
+     {31, 0x02, 165},
+     {41, 0x02, 165},
+     {56, 0x03, 165},
+     {3, 0x02, 166},
+     {6, 0x02, 166},
+     {10, 0x02, 166},
+     {15, 0x02, 166},
+     {24, 0x02, 166},
+     {31, 0x02, 166},
+     {41, 0x02, 166},
+     {56, 0x03, 166},
+    },
+    /* 165 */
+    {
+     {3, 0x02, 168},
+     {6, 0x02, 168},
+     {10, 0x02, 168},
+     {15, 0x02, 168},
+     {24, 0x02, 168},
+     {31, 0x02, 168},
+     {41, 0x02, 168},
+     {56, 0x03, 168},
+     {3, 0x02, 174},
+     {6, 0x02, 174},
+     {10, 0x02, 174},
+     {15, 0x02, 174},
+     {24, 0x02, 174},
+     {31, 0x02, 174},
+     {41, 0x02, 174},
+     {56, 0x03, 174},
+    },
+    /* 166 */
+    {
+     {2, 0x02, 175},
+     {9, 0x02, 175},
+     {23, 0x02, 175},
+     {40, 0x03, 175},
+     {2, 0x02, 180},
+     {9, 0x02, 180},
+     {23, 0x02, 180},
+     {40, 0x03, 180},
+     {2, 0x02, 182},
+     {9, 0x02, 182},
+     {23, 0x02, 182},
+     {40, 0x03, 182},
+     {2, 0x02, 183},
+     {9, 0x02, 183},
+     {23, 0x02, 183},
+     {40, 0x03, 183},
+    },
+    /* 167 */
+    {
+     {3, 0x02, 175},
+     {6, 0x02, 175},
+     {10, 0x02, 175},
+     {15, 0x02, 175},
+     {24, 0x02, 175},
+     {31, 0x02, 175},
+     {41, 0x02, 175},
+     {56, 0x03, 175},
+     {3, 0x02, 180},
+     {6, 0x02, 180},
+     {10, 0x02, 180},
+     {15, 0x02, 180},
+     {24, 0x02, 180},
+     {31, 0x02, 180},
+     {41, 0x02, 180},
+     {56, 0x03, 180},
+    },
+    /* 168 */
+    {
+     {3, 0x02, 182},
+     {6, 0x02, 182},
+     {10, 0x02, 182},
+     {15, 0x02, 182},
+     {24, 0x02, 182},
+     {31, 0x02, 182},
+     {41, 0x02, 182},
+     {56, 0x03, 182},
+     {3, 0x02, 183},
+     {6, 0x02, 183},
+     {10, 0x02, 183},
+     {15, 0x02, 183},
+     {24, 0x02, 183},
+     {31, 0x02, 183},
+     {41, 0x02, 183},
+     {56, 0x03, 183},
+    },
+    /* 169 */
+    {
+     {0, 0x03, 188},
+     {0, 0x03, 191},
+     {0, 0x03, 197},
+     {0, 0x03, 231},
+     {0, 0x03, 239},
+     {176, 0x00, 0},
+     {178, 0x00, 0},
+     {179, 0x00, 0},
+     {183, 0x00, 0},
+     {184, 0x00, 0},
+     {186, 0x00, 0},
+     {187, 0x00, 0},
+     {192, 0x00, 0},
+     {199, 0x00, 0},
+     {208, 0x00, 0},
+     {223, 0x00, 0},
+    },
+    /* 170 */
+    {
+     {1, 0x02, 188},
+     {22, 0x03, 188},
+     {1, 0x02, 191},
+     {22, 0x03, 191},
+     {1, 0x02, 197},
+     {22, 0x03, 197},
+     {1, 0x02, 231},
+     {22, 0x03, 231},
+     {1, 0x02, 239},
+     {22, 0x03, 239},
+     {0, 0x03, 9},
+     {0, 0x03, 142},
+     {0, 0x03, 144},
+     {0, 0x03, 145},
+     {0, 0x03, 148},
+     {0, 0x03, 159},
+    },
+    /* 171 */
+    {
+     {2, 0x02, 188},
+     {9, 0x02, 188},
+     {23, 0x02, 188},
+     {40, 0x03, 188},
+     {2, 0x02, 191},
+     {9, 0x02, 191},
+     {23, 0x02, 191},
+     {40, 0x03, 191},
+     {2, 0x02, 197},
+     {9, 0x02, 197},
+     {23, 0x02, 197},
+     {40, 0x03, 197},
+     {2, 0x02, 231},
+     {9, 0x02, 231},
+     {23, 0x02, 231},
+     {40, 0x03, 231},
+    },
+    /* 172 */
+    {
+     {3, 0x02, 188},
+     {6, 0x02, 188},
+     {10, 0x02, 188},
+     {15, 0x02, 188},
+     {24, 0x02, 188},
+     {31, 0x02, 188},
+     {41, 0x02, 188},
+     {56, 0x03, 188},
+     {3, 0x02, 191},
+     {6, 0x02, 191},
+     {10, 0x02, 191},
+     {15, 0x02, 191},
+     {24, 0x02, 191},
+     {31, 0x02, 191},
+     {41, 0x02, 191},
+     {56, 0x03, 191},
+    },
+    /* 173 */
+    {
+     {3, 0x02, 197},
+     {6, 0x02, 197},
+     {10, 0x02, 197},
+     {15, 0x02, 197},
+     {24, 0x02, 197},
+     {31, 0x02, 197},
+     {41, 0x02, 197},
+     {56, 0x03, 197},
+     {3, 0x02, 231},
+     {6, 0x02, 231},
+     {10, 0x02, 231},
+     {15, 0x02, 231},
+     {24, 0x02, 231},
+     {31, 0x02, 231},
+     {41, 0x02, 231},
+     {56, 0x03, 231},
+    },
+    /* 174 */
+    {
+     {2, 0x02, 239},
+     {9, 0x02, 239},
+     {23, 0x02, 239},
+     {40, 0x03, 239},
+     {1, 0x02, 9},
+     {22, 0x03, 9},
+     {1, 0x02, 142},
+     {22, 0x03, 142},
+     {1, 0x02, 144},
+     {22, 0x03, 144},
+     {1, 0x02, 145},
+     {22, 0x03, 145},
+     {1, 0x02, 148},
+     {22, 0x03, 148},
+     {1, 0x02, 159},
+     {22, 0x03, 159},
+    },
+    /* 175 */
+    {
+     {3, 0x02, 239},
+     {6, 0x02, 239},
+     {10, 0x02, 239},
+     {15, 0x02, 239},
+     {24, 0x02, 239},
+     {31, 0x02, 239},
+     {41, 0x02, 239},
+     {56, 0x03, 239},
+     {2, 0x02, 9},
+     {9, 0x02, 9},
+     {23, 0x02, 9},
+     {40, 0x03, 9},
+     {2, 0x02, 142},
+     {9, 0x02, 142},
+     {23, 0x02, 142},
+     {40, 0x03, 142},
+    },
+    /* 176 */
+    {
+     {3, 0x02, 9},
+     {6, 0x02, 9},
+     {10, 0x02, 9},
+     {15, 0x02, 9},
+     {24, 0x02, 9},
+     {31, 0x02, 9},
+     {41, 0x02, 9},
+     {56, 0x03, 9},
+     {3, 0x02, 142},
+     {6, 0x02, 142},
+     {10, 0x02, 142},
+     {15, 0x02, 142},
+     {24, 0x02, 142},
+     {31, 0x02, 142},
+     {41, 0x02, 142},
+     {56, 0x03, 142},
+    },
+    /* 177 */
+    {
+     {2, 0x02, 144},
+     {9, 0x02, 144},
+     {23, 0x02, 144},
+     {40, 0x03, 144},
+     {2, 0x02, 145},
+     {9, 0x02, 145},
+     {23, 0x02, 145},
+     {40, 0x03, 145},
+     {2, 0x02, 148},
+     {9, 0x02, 148},
+     {23, 0x02, 148},
+     {40, 0x03, 148},
+     {2, 0x02, 159},
+     {9, 0x02, 159},
+     {23, 0x02, 159},
+     {40, 0x03, 159},
+    },
+    /* 178 */
+    {
+     {3, 0x02, 144},
+     {6, 0x02, 144},
+     {10, 0x02, 144},
+     {15, 0x02, 144},
+     {24, 0x02, 144},
+     {31, 0x02, 144},
+     {41, 0x02, 144},
+     {56, 0x03, 144},
+     {3, 0x02, 145},
+     {6, 0x02, 145},
+     {10, 0x02, 145},
+     {15, 0x02, 145},
+     {24, 0x02, 145},
+     {31, 0x02, 145},
+     {41, 0x02, 145},
+     {56, 0x03, 145},
+    },
+    /* 179 */
+    {
+     {3, 0x02, 148},
+     {6, 0x02, 148},
+     {10, 0x02, 148},
+     {15, 0x02, 148},
+     {24, 0x02, 148},
+     {31, 0x02, 148},
+     {41, 0x02, 148},
+     {56, 0x03, 148},
+     {3, 0x02, 159},
+     {6, 0x02, 159},
+     {10, 0x02, 159},
+     {15, 0x02, 159},
+     {24, 0x02, 159},
+     {31, 0x02, 159},
+     {41, 0x02, 159},
+     {56, 0x03, 159},
+    },
+    /* 180 */
+    {
+     {0, 0x03, 171},
+     {0, 0x03, 206},
+     {0, 0x03, 215},
+     {0, 0x03, 225},
+     {0, 0x03, 236},
+     {0, 0x03, 237},
+     {188, 0x00, 0},
+     {189, 0x00, 0},
+     {193, 0x00, 0},
+     {196, 0x00, 0},
+     {200, 0x00, 0},
+     {203, 0x00, 0},
+     {209, 0x00, 0},
+     {216, 0x00, 0},
+     {224, 0x00, 0},
+     {238, 0x00, 0},
+    },
+    /* 181 */
+    {
+     {1, 0x02, 171},
+     {22, 0x03, 171},
+     {1, 0x02, 206},
+     {22, 0x03, 206},
+     {1, 0x02, 215},
+     {22, 0x03, 215},
+     {1, 0x02, 225},
+     {22, 0x03, 225},
+     {1, 0x02, 236},
+     {22, 0x03, 236},
+     {1, 0x02, 237},
+     {22, 0x03, 237},
+     {0, 0x03, 199},
+     {0, 0x03, 207},
+     {0, 0x03, 234},
+     {0, 0x03, 235},
+    },
+    /* 182 */
+    {
+     {2, 0x02, 171},
+     {9, 0x02, 171},
+     {23, 0x02, 171},
+     {40, 0x03, 171},
+     {2, 0x02, 206},
+     {9, 0x02, 206},
+     {23, 0x02, 206},
+     {40, 0x03, 206},
+     {2, 0x02, 215},
+     {9, 0x02, 215},
+     {23, 0x02, 215},
+     {40, 0x03, 215},
+     {2, 0x02, 225},
+     {9, 0x02, 225},
+     {23, 0x02, 225},
+     {40, 0x03, 225},
+    },
+    /* 183 */
+    {
+     {3, 0x02, 171},
+     {6, 0x02, 171},
+     {10, 0x02, 171},
+     {15, 0x02, 171},
+     {24, 0x02, 171},
+     {31, 0x02, 171},
+     {41, 0x02, 171},
+     {56, 0x03, 171},
+     {3, 0x02, 206},
+     {6, 0x02, 206},
+     {10, 0x02, 206},
+     {15, 0x02, 206},
+     {24, 0x02, 206},
+     {31, 0x02, 206},
+     {41, 0x02, 206},
+     {56, 0x03, 206},
+    },
+    /* 184 */
+    {
+     {3, 0x02, 215},
+     {6, 0x02, 215},
+     {10, 0x02, 215},
+     {15, 0x02, 215},
+     {24, 0x02, 215},
+     {31, 0x02, 215},
+     {41, 0x02, 215},
+     {56, 0x03, 215},
+     {3, 0x02, 225},
+     {6, 0x02, 225},
+     {10, 0x02, 225},
+     {15, 0x02, 225},
+     {24, 0x02, 225},
+     {31, 0x02, 225},
+     {41, 0x02, 225},
+     {56, 0x03, 225},
+    },
+    /* 185 */
+    {
+     {2, 0x02, 236},
+     {9, 0x02, 236},
+     {23, 0x02, 236},
+     {40, 0x03, 236},
+     {2, 0x02, 237},
+     {9, 0x02, 237},
+     {23, 0x02, 237},
+     {40, 0x03, 237},
+     {1, 0x02, 199},
+     {22, 0x03, 199},
+     {1, 0x02, 207},
+     {22, 0x03, 207},
+     {1, 0x02, 234},
+     {22, 0x03, 234},
+     {1, 0x02, 235},
+     {22, 0x03, 235},
+    },
+    /* 186 */
+    {
+     {3, 0x02, 236},
+     {6, 0x02, 236},
+     {10, 0x02, 236},
+     {15, 0x02, 236},
+     {24, 0x02, 236},
+     {31, 0x02, 236},
+     {41, 0x02, 236},
+     {56, 0x03, 236},
+     {3, 0x02, 237},
+     {6, 0x02, 237},
+     {10, 0x02, 237},
+     {15, 0x02, 237},
+     {24, 0x02, 237},
+     {31, 0x02, 237},
+     {41, 0x02, 237},
+     {56, 0x03, 237},
+    },
+    /* 187 */
+    {
+     {2, 0x02, 199},
+     {9, 0x02, 199},
+     {23, 0x02, 199},
+     {40, 0x03, 199},
+     {2, 0x02, 207},
+     {9, 0x02, 207},
+     {23, 0x02, 207},
+     {40, 0x03, 207},
+     {2, 0x02, 234},
+     {9, 0x02, 234},
+     {23, 0x02, 234},
+     {40, 0x03, 234},
+     {2, 0x02, 235},
+     {9, 0x02, 235},
+     {23, 0x02, 235},
+     {40, 0x03, 235},
+    },
+    /* 188 */
+    {
+     {3, 0x02, 199},
+     {6, 0x02, 199},
+     {10, 0x02, 199},
+     {15, 0x02, 199},
+     {24, 0x02, 199},
+     {31, 0x02, 199},
+     {41, 0x02, 199},
+     {56, 0x03, 199},
+     {3, 0x02, 207},
+     {6, 0x02, 207},
+     {10, 0x02, 207},
+     {15, 0x02, 207},
+     {24, 0x02, 207},
+     {31, 0x02, 207},
+     {41, 0x02, 207},
+     {56, 0x03, 207},
+    },
+    /* 189 */
+    {
+     {3, 0x02, 234},
+     {6, 0x02, 234},
+     {10, 0x02, 234},
+     {15, 0x02, 234},
+     {24, 0x02, 234},
+     {31, 0x02, 234},
+     {41, 0x02, 234},
+     {56, 0x03, 234},
+     {3, 0x02, 235},
+     {6, 0x02, 235},
+     {10, 0x02, 235},
+     {15, 0x02, 235},
+     {24, 0x02, 235},
+     {31, 0x02, 235},
+     {41, 0x02, 235},
+     {56, 0x03, 235},
+    },
+    /* 190 */
+    {
+     {194, 0x00, 0},
+     {195, 0x00, 0},
+     {197, 0x00, 0},
+     {198, 0x00, 0},
+     {201, 0x00, 0},
+     {202, 0x00, 0},
+     {204, 0x00, 0},
+     {205, 0x00, 0},
+     {210, 0x00, 0},
+     {213, 0x00, 0},
+     {217, 0x00, 0},
+     {220, 0x00, 0},
+     {225, 0x00, 0},
+     {231, 0x00, 0},
+     {239, 0x00, 0},
+     {246, 0x00, 0},
+    },
+    /* 191 */
+    {
+     {0, 0x03, 192},
+     {0, 0x03, 193},
+     {0, 0x03, 200},
+     {0, 0x03, 201},
+     {0, 0x03, 202},
+     {0, 0x03, 205},
+     {0, 0x03, 210},
+     {0, 0x03, 213},
+     {0, 0x03, 218},
+     {0, 0x03, 219},
+     {0, 0x03, 238},
+     {0, 0x03, 240},
+     {0, 0x03, 242},
+     {0, 0x03, 243},
+     {0, 0x03, 255},
+     {206, 0x00, 0},
+    },
+    /* 192 */
+    {
+     {1, 0x02, 192},
+     {22, 0x03, 192},
+     {1, 0x02, 193},
+     {22, 0x03, 193},
+     {1, 0x02, 200},
+     {22, 0x03, 200},
+     {1, 0x02, 201},
+     {22, 0x03, 201},
+     {1, 0x02, 202},
+     {22, 0x03, 202},
+     {1, 0x02, 205},
+     {22, 0x03, 205},
+     {1, 0x02, 210},
+     {22, 0x03, 210},
+     {1, 0x02, 213},
+     {22, 0x03, 213},
+    },
+    /* 193 */
+    {
+     {2, 0x02, 192},
+     {9, 0x02, 192},
+     {23, 0x02, 192},
+     {40, 0x03, 192},
+     {2, 0x02, 193},
+     {9, 0x02, 193},
+     {23, 0x02, 193},
+     {40, 0x03, 193},
+     {2, 0x02, 200},
+     {9, 0x02, 200},
+     {23, 0x02, 200},
+     {40, 0x03, 200},
+     {2, 0x02, 201},
+     {9, 0x02, 201},
+     {23, 0x02, 201},
+     {40, 0x03, 201},
+    },
+    /* 194 */
+    {
+     {3, 0x02, 192},
+     {6, 0x02, 192},
+     {10, 0x02, 192},
+     {15, 0x02, 192},
+     {24, 0x02, 192},
+     {31, 0x02, 192},
+     {41, 0x02, 192},
+     {56, 0x03, 192},
+     {3, 0x02, 193},
+     {6, 0x02, 193},
+     {10, 0x02, 193},
+     {15, 0x02, 193},
+     {24, 0x02, 193},
+     {31, 0x02, 193},
+     {41, 0x02, 193},
+     {56, 0x03, 193},
+    },
+    /* 195 */
+    {
+     {3, 0x02, 200},
+     {6, 0x02, 200},
+     {10, 0x02, 200},
+     {15, 0x02, 200},
+     {24, 0x02, 200},
+     {31, 0x02, 200},
+     {41, 0x02, 200},
+     {56, 0x03, 200},
+     {3, 0x02, 201},
+     {6, 0x02, 201},
+     {10, 0x02, 201},
+     {15, 0x02, 201},
+     {24, 0x02, 201},
+     {31, 0x02, 201},
+     {41, 0x02, 201},
+     {56, 0x03, 201},
+    },
+    /* 196 */
+    {
+     {2, 0x02, 202},
+     {9, 0x02, 202},
+     {23, 0x02, 202},
+     {40, 0x03, 202},
+     {2, 0x02, 205},
+     {9, 0x02, 205},
+     {23, 0x02, 205},
+     {40, 0x03, 205},
+     {2, 0x02, 210},
+     {9, 0x02, 210},
+     {23, 0x02, 210},
+     {40, 0x03, 210},
+     {2, 0x02, 213},
+     {9, 0x02, 213},
+     {23, 0x02, 213},
+     {40, 0x03, 213},
+    },
+    /* 197 */
+    {
+     {3, 0x02, 202},
+     {6, 0x02, 202},
+     {10, 0x02, 202},
+     {15, 0x02, 202},
+     {24, 0x02, 202},
+     {31, 0x02, 202},
+     {41, 0x02, 202},
+     {56, 0x03, 202},
+     {3, 0x02, 205},
+     {6, 0x02, 205},
+     {10, 0x02, 205},
+     {15, 0x02, 205},
+     {24, 0x02, 205},
+     {31, 0x02, 205},
+     {41, 0x02, 205},
+     {56, 0x03, 205},
+    },
+    /* 198 */
+    {
+     {3, 0x02, 210},
+     {6, 0x02, 210},
+     {10, 0x02, 210},
+     {15, 0x02, 210},
+     {24, 0x02, 210},
+     {31, 0x02, 210},
+     {41, 0x02, 210},
+     {56, 0x03, 210},
+     {3, 0x02, 213},
+     {6, 0x02, 213},
+     {10, 0x02, 213},
+     {15, 0x02, 213},
+     {24, 0x02, 213},
+     {31, 0x02, 213},
+     {41, 0x02, 213},
+     {56, 0x03, 213},
+    },
+    /* 199 */
+    {
+     {1, 0x02, 218},
+     {22, 0x03, 218},
+     {1, 0x02, 219},
+     {22, 0x03, 219},
+     {1, 0x02, 238},
+     {22, 0x03, 238},
+     {1, 0x02, 240},
+     {22, 0x03, 240},
+     {1, 0x02, 242},
+     {22, 0x03, 242},
+     {1, 0x02, 243},
+     {22, 0x03, 243},
+     {1, 0x02, 255},
+     {22, 0x03, 255},
+     {0, 0x03, 203},
+     {0, 0x03, 204},
+    },
+    /* 200 */
+    {
+     {2, 0x02, 218},
+     {9, 0x02, 218},
+     {23, 0x02, 218},
+     {40, 0x03, 218},
+     {2, 0x02, 219},
+     {9, 0x02, 219},
+     {23, 0x02, 219},
+     {40, 0x03, 219},
+     {2, 0x02, 238},
+     {9, 0x02, 238},
+     {23, 0x02, 238},
+     {40, 0x03, 238},
+     {2, 0x02, 240},
+     {9, 0x02, 240},
+     {23, 0x02, 240},
+     {40, 0x03, 240},
+    },
+    /* 201 */
+    {
+     {3, 0x02, 218},
+     {6, 0x02, 218},
+     {10, 0x02, 218},
+     {15, 0x02, 218},
+     {24, 0x02, 218},
+     {31, 0x02, 218},
+     {41, 0x02, 218},
+     {56, 0x03, 218},
+     {3, 0x02, 219},
+     {6, 0x02, 219},
+     {10, 0x02, 219},
+     {15, 0x02, 219},
+     {24, 0x02, 219},
+     {31, 0x02, 219},
+     {41, 0x02, 219},
+     {56, 0x03, 219},
+    },
+    /* 202 */
+    {
+     {3, 0x02, 238},
+     {6, 0x02, 238},
+     {10, 0x02, 238},
+     {15, 0x02, 238},
+     {24, 0x02, 238},
+     {31, 0x02, 238},
+     {41, 0x02, 238},
+     {56, 0x03, 238},
+     {3, 0x02, 240},
+     {6, 0x02, 240},
+     {10, 0x02, 240},
+     {15, 0x02, 240},
+     {24, 0x02, 240},
+     {31, 0x02, 240},
+     {41, 0x02, 240},
+     {56, 0x03, 240},
+    },
+    /* 203 */
+    {
+     {2, 0x02, 242},
+     {9, 0x02, 242},
+     {23, 0x02, 242},
+     {40, 0x03, 242},
+     {2, 0x02, 243},
+     {9, 0x02, 243},
+     {23, 0x02, 243},
+     {40, 0x03, 243},
+     {2, 0x02, 255},
+     {9, 0x02, 255},
+     {23, 0x02, 255},
+     {40, 0x03, 255},
+     {1, 0x02, 203},
+     {22, 0x03, 203},
+     {1, 0x02, 204},
+     {22, 0x03, 204},
+    },
+    /* 204 */
+    {
+     {3, 0x02, 242},
+     {6, 0x02, 242},
+     {10, 0x02, 242},
+     {15, 0x02, 242},
+     {24, 0x02, 242},
+     {31, 0x02, 242},
+     {41, 0x02, 242},
+     {56, 0x03, 242},
+     {3, 0x02, 243},
+     {6, 0x02, 243},
+     {10, 0x02, 243},
+     {15, 0x02, 243},
+     {24, 0x02, 243},
+     {31, 0x02, 243},
+     {41, 0x02, 243},
+     {56, 0x03, 243},
+    },
+    /* 205 */
+    {
+     {3, 0x02, 255},
+     {6, 0x02, 255},
+     {10, 0x02, 255},
+     {15, 0x02, 255},
+     {24, 0x02, 255},
+     {31, 0x02, 255},
+     {41, 0x02, 255},
+     {56, 0x03, 255},
+     {2, 0x02, 203},
+     {9, 0x02, 203},
+     {23, 0x02, 203},
+     {40, 0x03, 203},
+     {2, 0x02, 204},
+     {9, 0x02, 204},
+     {23, 0x02, 204},
+     {40, 0x03, 204},
+    },
+    /* 206 */
+    {
+     {3, 0x02, 203},
+     {6, 0x02, 203},
+     {10, 0x02, 203},
+     {15, 0x02, 203},
+     {24, 0x02, 203},
+     {31, 0x02, 203},
+     {41, 0x02, 203},
+     {56, 0x03, 203},
+     {3, 0x02, 204},
+     {6, 0x02, 204},
+     {10, 0x02, 204},
+     {15, 0x02, 204},
+     {24, 0x02, 204},
+     {31, 0x02, 204},
+     {41, 0x02, 204},
+     {56, 0x03, 204},
+    },
+    /* 207 */
+    {
+     {211, 0x00, 0},
+     {212, 0x00, 0},
+     {214, 0x00, 0},
+     {215, 0x00, 0},
+     {218, 0x00, 0},
+     {219, 0x00, 0},
+     {221, 0x00, 0},
+     {222, 0x00, 0},
+     {226, 0x00, 0},
+     {228, 0x00, 0},
+     {232, 0x00, 0},
+     {235, 0x00, 0},
+     {240, 0x00, 0},
+     {243, 0x00, 0},
+     {247, 0x00, 0},
+     {250, 0x00, 0},
+    },
+    /* 208 */
+    {
+     {0, 0x03, 211},
+     {0, 0x03, 212},
+     {0, 0x03, 214},
+     {0, 0x03, 221},
+     {0, 0x03, 222},
+     {0, 0x03, 223},
+     {0, 0x03, 241},
+     {0, 0x03, 244},
+     {0, 0x03, 245},
+     {0, 0x03, 246},
+     {0, 0x03, 247},
+     {0, 0x03, 248},
+     {0, 0x03, 250},
+     {0, 0x03, 251},
+     {0, 0x03, 252},
+     {0, 0x03, 253},
+    },
+    /* 209 */
+    {
+     {1, 0x02, 211},
+     {22, 0x03, 211},
+     {1, 0x02, 212},
+     {22, 0x03, 212},
+     {1, 0x02, 214},
+     {22, 0x03, 214},
+     {1, 0x02, 221},
+     {22, 0x03, 221},
+     {1, 0x02, 222},
+     {22, 0x03, 222},
+     {1, 0x02, 223},
+     {22, 0x03, 223},
+     {1, 0x02, 241},
+     {22, 0x03, 241},
+     {1, 0x02, 244},
+     {22, 0x03, 244},
+    },
+    /* 210 */
+    {
+     {2, 0x02, 211},
+     {9, 0x02, 211},
+     {23, 0x02, 211},
+     {40, 0x03, 211},
+     {2, 0x02, 212},
+     {9, 0x02, 212},
+     {23, 0x02, 212},
+     {40, 0x03, 212},
+     {2, 0x02, 214},
+     {9, 0x02, 214},
+     {23, 0x02, 214},
+     {40, 0x03, 214},
+     {2, 0x02, 221},
+     {9, 0x02, 221},
+     {23, 0x02, 221},
+     {40, 0x03, 221},
+    },
+    /* 211 */
+    {
+     {3, 0x02, 211},
+     {6, 0x02, 211},
+     {10, 0x02, 211},
+     {15, 0x02, 211},
+     {24, 0x02, 211},
+     {31, 0x02, 211},
+     {41, 0x02, 211},
+     {56, 0x03, 211},
+     {3, 0x02, 212},
+     {6, 0x02, 212},
+     {10, 0x02, 212},
+     {15, 0x02, 212},
+     {24, 0x02, 212},
+     {31, 0x02, 212},
+     {41, 0x02, 212},
+     {56, 0x03, 212},
+    },
+    /* 212 */
+    {
+     {3, 0x02, 214},
+     {6, 0x02, 214},
+     {10, 0x02, 214},
+     {15, 0x02, 214},
+     {24, 0x02, 214},
+     {31, 0x02, 214},
+     {41, 0x02, 214},
+     {56, 0x03, 214},
+     {3, 0x02, 221},
+     {6, 0x02, 221},
+     {10, 0x02, 221},
+     {15, 0x02, 221},
+     {24, 0x02, 221},
+     {31, 0x02, 221},
+     {41, 0x02, 221},
+     {56, 0x03, 221},
+    },
+    /* 213 */
+    {
+     {2, 0x02, 222},
+     {9, 0x02, 222},
+     {23, 0x02, 222},
+     {40, 0x03, 222},
+     {2, 0x02, 223},
+     {9, 0x02, 223},
+     {23, 0x02, 223},
+     {40, 0x03, 223},
+     {2, 0x02, 241},
+     {9, 0x02, 241},
+     {23, 0x02, 241},
+     {40, 0x03, 241},
+     {2, 0x02, 244},
+     {9, 0x02, 244},
+     {23, 0x02, 244},
+     {40, 0x03, 244},
+    },
+    /* 214 */
+    {
+     {3, 0x02, 222},
+     {6, 0x02, 222},
+     {10, 0x02, 222},
+     {15, 0x02, 222},
+     {24, 0x02, 222},
+     {31, 0x02, 222},
+     {41, 0x02, 222},
+     {56, 0x03, 222},
+     {3, 0x02, 223},
+     {6, 0x02, 223},
+     {10, 0x02, 223},
+     {15, 0x02, 223},
+     {24, 0x02, 223},
+     {31, 0x02, 223},
+     {41, 0x02, 223},
+     {56, 0x03, 223},
+    },
+    /* 215 */
+    {
+     {3, 0x02, 241},
+     {6, 0x02, 241},
+     {10, 0x02, 241},
+     {15, 0x02, 241},
+     {24, 0x02, 241},
+     {31, 0x02, 241},
+     {41, 0x02, 241},
+     {56, 0x03, 241},
+     {3, 0x02, 244},
+     {6, 0x02, 244},
+     {10, 0x02, 244},
+     {15, 0x02, 244},
+     {24, 0x02, 244},
+     {31, 0x02, 244},
+     {41, 0x02, 244},
+     {56, 0x03, 244},
+    },
+    /* 216 */
+    {
+     {1, 0x02, 245},
+     {22, 0x03, 245},
+     {1, 0x02, 246},
+     {22, 0x03, 246},
+     {1, 0x02, 247},
+     {22, 0x03, 247},
+     {1, 0x02, 248},
+     {22, 0x03, 248},
+     {1, 0x02, 250},
+     {22, 0x03, 250},
+     {1, 0x02, 251},
+     {22, 0x03, 251},
+     {1, 0x02, 252},
+     {22, 0x03, 252},
+     {1, 0x02, 253},
+     {22, 0x03, 253},
+    },
+    /* 217 */
+    {
+     {2, 0x02, 245},
+     {9, 0x02, 245},
+     {23, 0x02, 245},
+     {40, 0x03, 245},
+     {2, 0x02, 246},
+     {9, 0x02, 246},
+     {23, 0x02, 246},
+     {40, 0x03, 246},
+     {2, 0x02, 247},
+     {9, 0x02, 247},
+     {23, 0x02, 247},
+     {40, 0x03, 247},
+     {2, 0x02, 248},
+     {9, 0x02, 248},
+     {23, 0x02, 248},
+     {40, 0x03, 248},
+    },
+    /* 218 */
+    {
+     {3, 0x02, 245},
+     {6, 0x02, 245},
+     {10, 0x02, 245},
+     {15, 0x02, 245},
+     {24, 0x02, 245},
+     {31, 0x02, 245},
+     {41, 0x02, 245},
+     {56, 0x03, 245},
+     {3, 0x02, 246},
+     {6, 0x02, 246},
+     {10, 0x02, 246},
+     {15, 0x02, 246},
+     {24, 0x02, 246},
+     {31, 0x02, 246},
+     {41, 0x02, 246},
+     {56, 0x03, 246},
+    },
+    /* 219 */
+    {
+     {3, 0x02, 247},
+     {6, 0x02, 247},
+     {10, 0x02, 247},
+     {15, 0x02, 247},
+     {24, 0x02, 247},
+     {31, 0x02, 247},
+     {41, 0x02, 247},
+     {56, 0x03, 247},
+     {3, 0x02, 248},
+     {6, 0x02, 248},
+     {10, 0x02, 248},
+     {15, 0x02, 248},
+     {24, 0x02, 248},
+     {31, 0x02, 248},
+     {41, 0x02, 248},
+     {56, 0x03, 248},
+    },
+    /* 220 */
+    {
+     {2, 0x02, 250},
+     {9, 0x02, 250},
+     {23, 0x02, 250},
+     {40, 0x03, 250},
+     {2, 0x02, 251},
+     {9, 0x02, 251},
+     {23, 0x02, 251},
+     {40, 0x03, 251},
+     {2, 0x02, 252},
+     {9, 0x02, 252},
+     {23, 0x02, 252},
+     {40, 0x03, 252},
+     {2, 0x02, 253},
+     {9, 0x02, 253},
+     {23, 0x02, 253},
+     {40, 0x03, 253},
+    },
+    /* 221 */
+    {
+     {3, 0x02, 250},
+     {6, 0x02, 250},
+     {10, 0x02, 250},
+     {15, 0x02, 250},
+     {24, 0x02, 250},
+     {31, 0x02, 250},
+     {41, 0x02, 250},
+     {56, 0x03, 250},
+     {3, 0x02, 251},
+     {6, 0x02, 251},
+     {10, 0x02, 251},
+     {15, 0x02, 251},
+     {24, 0x02, 251},
+     {31, 0x02, 251},
+     {41, 0x02, 251},
+     {56, 0x03, 251},
+    },
+    /* 222 */
+    {
+     {3, 0x02, 252},
+     {6, 0x02, 252},
+     {10, 0x02, 252},
+     {15, 0x02, 252},
+     {24, 0x02, 252},
+     {31, 0x02, 252},
+     {41, 0x02, 252},
+     {56, 0x03, 252},
+     {3, 0x02, 253},
+     {6, 0x02, 253},
+     {10, 0x02, 253},
+     {15, 0x02, 253},
+     {24, 0x02, 253},
+     {31, 0x02, 253},
+     {41, 0x02, 253},
+     {56, 0x03, 253},
+    },
+    /* 223 */
+    {
+     {0, 0x03, 254},
+     {227, 0x00, 0},
+     {229, 0x00, 0},
+     {230, 0x00, 0},
+     {233, 0x00, 0},
+     {234, 0x00, 0},
+     {236, 0x00, 0},
+     {237, 0x00, 0},
+     {241, 0x00, 0},
+     {242, 0x00, 0},
+     {244, 0x00, 0},
+     {245, 0x00, 0},
+     {248, 0x00, 0},
+     {249, 0x00, 0},
+     {251, 0x00, 0},
+     {252, 0x00, 0},
+    },
+    /* 224 */
+    {
+     {1, 0x02, 254},
+     {22, 0x03, 254},
+     {0, 0x03, 2},
+     {0, 0x03, 3},
+     {0, 0x03, 4},
+     {0, 0x03, 5},
+     {0, 0x03, 6},
+     {0, 0x03, 7},
+     {0, 0x03, 8},
+     {0, 0x03, 11},
+     {0, 0x03, 12},
+     {0, 0x03, 14},
+     {0, 0x03, 15},
+     {0, 0x03, 16},
+     {0, 0x03, 17},
+     {0, 0x03, 18},
+    },
+    /* 225 */
+    {
+     {2, 0x02, 254},
+     {9, 0x02, 254},
+     {23, 0x02, 254},
+     {40, 0x03, 254},
+     {1, 0x02, 2},
+     {22, 0x03, 2},
+     {1, 0x02, 3},
+     {22, 0x03, 3},
+     {1, 0x02, 4},
+     {22, 0x03, 4},
+     {1, 0x02, 5},
+     {22, 0x03, 5},
+     {1, 0x02, 6},
+     {22, 0x03, 6},
+     {1, 0x02, 7},
+     {22, 0x03, 7},
+    },
+    /* 226 */
+    {
+     {3, 0x02, 254},
+     {6, 0x02, 254},
+     {10, 0x02, 254},
+     {15, 0x02, 254},
+     {24, 0x02, 254},
+     {31, 0x02, 254},
+     {41, 0x02, 254},
+     {56, 0x03, 254},
+     {2, 0x02, 2},
+     {9, 0x02, 2},
+     {23, 0x02, 2},
+     {40, 0x03, 2},
+     {2, 0x02, 3},
+     {9, 0x02, 3},
+     {23, 0x02, 3},
+     {40, 0x03, 3},
+    },
+    /* 227 */
+    {
+     {3, 0x02, 2},
+     {6, 0x02, 2},
+     {10, 0x02, 2},
+     {15, 0x02, 2},
+     {24, 0x02, 2},
+     {31, 0x02, 2},
+     {41, 0x02, 2},
+     {56, 0x03, 2},
+     {3, 0x02, 3},
+     {6, 0x02, 3},
+     {10, 0x02, 3},
+     {15, 0x02, 3},
+     {24, 0x02, 3},
+     {31, 0x02, 3},
+     {41, 0x02, 3},
+     {56, 0x03, 3},
+    },
+    /* 228 */
+    {
+     {2, 0x02, 4},
+     {9, 0x02, 4},
+     {23, 0x02, 4},
+     {40, 0x03, 4},
+     {2, 0x02, 5},
+     {9, 0x02, 5},
+     {23, 0x02, 5},
+     {40, 0x03, 5},
+     {2, 0x02, 6},
+     {9, 0x02, 6},
+     {23, 0x02, 6},
+     {40, 0x03, 6},
+     {2, 0x02, 7},
+     {9, 0x02, 7},
+     {23, 0x02, 7},
+     {40, 0x03, 7},
+    },
+    /* 229 */
+    {
+     {3, 0x02, 4},
+     {6, 0x02, 4},
+     {10, 0x02, 4},
+     {15, 0x02, 4},
+     {24, 0x02, 4},
+     {31, 0x02, 4},
+     {41, 0x02, 4},
+     {56, 0x03, 4},
+     {3, 0x02, 5},
+     {6, 0x02, 5},
+     {10, 0x02, 5},
+     {15, 0x02, 5},
+     {24, 0x02, 5},
+     {31, 0x02, 5},
+     {41, 0x02, 5},
+     {56, 0x03, 5},
+    },
+    /* 230 */
+    {
+     {3, 0x02, 6},
+     {6, 0x02, 6},
+     {10, 0x02, 6},
+     {15, 0x02, 6},
+     {24, 0x02, 6},
+     {31, 0x02, 6},
+     {41, 0x02, 6},
+     {56, 0x03, 6},
+     {3, 0x02, 7},
+     {6, 0x02, 7},
+     {10, 0x02, 7},
+     {15, 0x02, 7},
+     {24, 0x02, 7},
+     {31, 0x02, 7},
+     {41, 0x02, 7},
+     {56, 0x03, 7},
+    },
+    /* 231 */
+    {
+     {1, 0x02, 8},
+     {22, 0x03, 8},
+     {1, 0x02, 11},
+     {22, 0x03, 11},
+     {1, 0x02, 12},
+     {22, 0x03, 12},
+     {1, 0x02, 14},
+     {22, 0x03, 14},
+     {1, 0x02, 15},
+     {22, 0x03, 15},
+     {1, 0x02, 16},
+     {22, 0x03, 16},
+     {1, 0x02, 17},
+     {22, 0x03, 17},
+     {1, 0x02, 18},
+     {22, 0x03, 18},
+    },
+    /* 232 */
+    {
+     {2, 0x02, 8},
+     {9, 0x02, 8},
+     {23, 0x02, 8},
+     {40, 0x03, 8},
+     {2, 0x02, 11},
+     {9, 0x02, 11},
+     {23, 0x02, 11},
+     {40, 0x03, 11},
+     {2, 0x02, 12},
+     {9, 0x02, 12},
+     {23, 0x02, 12},
+     {40, 0x03, 12},
+     {2, 0x02, 14},
+     {9, 0x02, 14},
+     {23, 0x02, 14},
+     {40, 0x03, 14},
+    },
+    /* 233 */
+    {
+     {3, 0x02, 8},
+     {6, 0x02, 8},
+     {10, 0x02, 8},
+     {15, 0x02, 8},
+     {24, 0x02, 8},
+     {31, 0x02, 8},
+     {41, 0x02, 8},
+     {56, 0x03, 8},
+     {3, 0x02, 11},
+     {6, 0x02, 11},
+     {10, 0x02, 11},
+     {15, 0x02, 11},
+     {24, 0x02, 11},
+     {31, 0x02, 11},
+     {41, 0x02, 11},
+     {56, 0x03, 11},
+    },
+    /* 234 */
+    {
+     {3, 0x02, 12},
+     {6, 0x02, 12},
+     {10, 0x02, 12},
+     {15, 0x02, 12},
+     {24, 0x02, 12},
+     {31, 0x02, 12},
+     {41, 0x02, 12},
+     {56, 0x03, 12},
+     {3, 0x02, 14},
+     {6, 0x02, 14},
+     {10, 0x02, 14},
+     {15, 0x02, 14},
+     {24, 0x02, 14},
+     {31, 0x02, 14},
+     {41, 0x02, 14},
+     {56, 0x03, 14},
+    },
+    /* 235 */
+    {
+     {2, 0x02, 15},
+     {9, 0x02, 15},
+     {23, 0x02, 15},
+     {40, 0x03, 15},
+     {2, 0x02, 16},
+     {9, 0x02, 16},
+     {23, 0x02, 16},
+     {40, 0x03, 16},
+     {2, 0x02, 17},
+     {9, 0x02, 17},
+     {23, 0x02, 17},
+     {40, 0x03, 17},
+     {2, 0x02, 18},
+     {9, 0x02, 18},
+     {23, 0x02, 18},
+     {40, 0x03, 18},
+    },
+    /* 236 */
+    {
+     {3, 0x02, 15},
+     {6, 0x02, 15},
+     {10, 0x02, 15},
+     {15, 0x02, 15},
+     {24, 0x02, 15},
+     {31, 0x02, 15},
+     {41, 0x02, 15},
+     {56, 0x03, 15},
+     {3, 0x02, 16},
+     {6, 0x02, 16},
+     {10, 0x02, 16},
+     {15, 0x02, 16},
+     {24, 0x02, 16},
+     {31, 0x02, 16},
+     {41, 0x02, 16},
+     {56, 0x03, 16},
+    },
+    /* 237 */
+    {
+     {3, 0x02, 17},
+     {6, 0x02, 17},
+     {10, 0x02, 17},
+     {15, 0x02, 17},
+     {24, 0x02, 17},
+     {31, 0x02, 17},
+     {41, 0x02, 17},
+     {56, 0x03, 17},
+     {3, 0x02, 18},
+     {6, 0x02, 18},
+     {10, 0x02, 18},
+     {15, 0x02, 18},
+     {24, 0x02, 18},
+     {31, 0x02, 18},
+     {41, 0x02, 18},
+     {56, 0x03, 18},
+    },
+    /* 238 */
+    {
+     {0, 0x03, 19},
+     {0, 0x03, 20},
+     {0, 0x03, 21},
+     {0, 0x03, 23},
+     {0, 0x03, 24},
+     {0, 0x03, 25},
+     {0, 0x03, 26},
+     {0, 0x03, 27},
+     {0, 0x03, 28},
+     {0, 0x03, 29},
+     {0, 0x03, 30},
+     {0, 0x03, 31},
+     {0, 0x03, 127},
+     {0, 0x03, 220},
+     {0, 0x03, 249},
+     {253, 0x00, 0},
+    },
+    /* 239 */
+    {
+     {1, 0x02, 19},
+     {22, 0x03, 19},
+     {1, 0x02, 20},
+     {22, 0x03, 20},
+     {1, 0x02, 21},
+     {22, 0x03, 21},
+     {1, 0x02, 23},
+     {22, 0x03, 23},
+     {1, 0x02, 24},
+     {22, 0x03, 24},
+     {1, 0x02, 25},
+     {22, 0x03, 25},
+     {1, 0x02, 26},
+     {22, 0x03, 26},
+     {1, 0x02, 27},
+     {22, 0x03, 27},
+    },
+    /* 240 */
+    {
+     {2, 0x02, 19},
+     {9, 0x02, 19},
+     {23, 0x02, 19},
+     {40, 0x03, 19},
+     {2, 0x02, 20},
+     {9, 0x02, 20},
+     {23, 0x02, 20},
+     {40, 0x03, 20},
+     {2, 0x02, 21},
+     {9, 0x02, 21},
+     {23, 0x02, 21},
+     {40, 0x03, 21},
+     {2, 0x02, 23},
+     {9, 0x02, 23},
+     {23, 0x02, 23},
+     {40, 0x03, 23},
+    },
+    /* 241 */
+    {
+     {3, 0x02, 19},
+     {6, 0x02, 19},
+     {10, 0x02, 19},
+     {15, 0x02, 19},
+     {24, 0x02, 19},
+     {31, 0x02, 19},
+     {41, 0x02, 19},
+     {56, 0x03, 19},
+     {3, 0x02, 20},
+     {6, 0x02, 20},
+     {10, 0x02, 20},
+     {15, 0x02, 20},
+     {24, 0x02, 20},
+     {31, 0x02, 20},
+     {41, 0x02, 20},
+     {56, 0x03, 20},
+    },
+    /* 242 */
+    {
+     {3, 0x02, 21},
+     {6, 0x02, 21},
+     {10, 0x02, 21},
+     {15, 0x02, 21},
+     {24, 0x02, 21},
+     {31, 0x02, 21},
+     {41, 0x02, 21},
+     {56, 0x03, 21},
+     {3, 0x02, 23},
+     {6, 0x02, 23},
+     {10, 0x02, 23},
+     {15, 0x02, 23},
+     {24, 0x02, 23},
+     {31, 0x02, 23},
+     {41, 0x02, 23},
+     {56, 0x03, 23},
+    },
+    /* 243 */
+    {
+     {2, 0x02, 24},
+     {9, 0x02, 24},
+     {23, 0x02, 24},
+     {40, 0x03, 24},
+     {2, 0x02, 25},
+     {9, 0x02, 25},
+     {23, 0x02, 25},
+     {40, 0x03, 25},
+     {2, 0x02, 26},
+     {9, 0x02, 26},
+     {23, 0x02, 26},
+     {40, 0x03, 26},
+     {2, 0x02, 27},
+     {9, 0x02, 27},
+     {23, 0x02, 27},
+     {40, 0x03, 27},
+    },
+    /* 244 */
+    {
+     {3, 0x02, 24},
+     {6, 0x02, 24},
+     {10, 0x02, 24},
+     {15, 0x02, 24},
+     {24, 0x02, 24},
+     {31, 0x02, 24},
+     {41, 0x02, 24},
+     {56, 0x03, 24},
+     {3, 0x02, 25},
+     {6, 0x02, 25},
+     {10, 0x02, 25},
+     {15, 0x02, 25},
+     {24, 0x02, 25},
+     {31, 0x02, 25},
+     {41, 0x02, 25},
+     {56, 0x03, 25},
+    },
+    /* 245 */
+    {
+     {3, 0x02, 26},
+     {6, 0x02, 26},
+     {10, 0x02, 26},
+     {15, 0x02, 26},
+     {24, 0x02, 26},
+     {31, 0x02, 26},
+     {41, 0x02, 26},
+     {56, 0x03, 26},
+     {3, 0x02, 27},
+     {6, 0x02, 27},
+     {10, 0x02, 27},
+     {15, 0x02, 27},
+     {24, 0x02, 27},
+     {31, 0x02, 27},
+     {41, 0x02, 27},
+     {56, 0x03, 27},
+    },
+    /* 246 */
+    {
+     {1, 0x02, 28},
+     {22, 0x03, 28},
+     {1, 0x02, 29},
+     {22, 0x03, 29},
+     {1, 0x02, 30},
+     {22, 0x03, 30},
+     {1, 0x02, 31},
+     {22, 0x03, 31},
+     {1, 0x02, 127},
+     {22, 0x03, 127},
+     {1, 0x02, 220},
+     {22, 0x03, 220},
+     {1, 0x02, 249},
+     {22, 0x03, 249},
+     {254, 0x00, 0},
+     {255, 0x00, 0},
+    },
+    /* 247 */
+    {
+     {2, 0x02, 28},
+     {9, 0x02, 28},
+     {23, 0x02, 28},
+     {40, 0x03, 28},
+     {2, 0x02, 29},
+     {9, 0x02, 29},
+     {23, 0x02, 29},
+     {40, 0x03, 29},
+     {2, 0x02, 30},
+     {9, 0x02, 30},
+     {23, 0x02, 30},
+     {40, 0x03, 30},
+     {2, 0x02, 31},
+     {9, 0x02, 31},
+     {23, 0x02, 31},
+     {40, 0x03, 31},
+    },
+    /* 248 */
+    {
+     {3, 0x02, 28},
+     {6, 0x02, 28},
+     {10, 0x02, 28},
+     {15, 0x02, 28},
+     {24, 0x02, 28},
+     {31, 0x02, 28},
+     {41, 0x02, 28},
+     {56, 0x03, 28},
+     {3, 0x02, 29},
+     {6, 0x02, 29},
+     {10, 0x02, 29},
+     {15, 0x02, 29},
+     {24, 0x02, 29},
+     {31, 0x02, 29},
+     {41, 0x02, 29},
+     {56, 0x03, 29},
+    },
+    /* 249 */
+    {
+     {3, 0x02, 30},
+     {6, 0x02, 30},
+     {10, 0x02, 30},
+     {15, 0x02, 30},
+     {24, 0x02, 30},
+     {31, 0x02, 30},
+     {41, 0x02, 30},
+     {56, 0x03, 30},
+     {3, 0x02, 31},
+     {6, 0x02, 31},
+     {10, 0x02, 31},
+     {15, 0x02, 31},
+     {24, 0x02, 31},
+     {31, 0x02, 31},
+     {41, 0x02, 31},
+     {56, 0x03, 31},
+    },
+    /* 250 */
+    {
+     {2, 0x02, 127},
+     {9, 0x02, 127},
+     {23, 0x02, 127},
+     {40, 0x03, 127},
+     {2, 0x02, 220},
+     {9, 0x02, 220},
+     {23, 0x02, 220},
+     {40, 0x03, 220},
+     {2, 0x02, 249},
+     {9, 0x02, 249},
+     {23, 0x02, 249},
+     {40, 0x03, 249},
+     {0, 0x03, 10},
+     {0, 0x03, 13},
+     {0, 0x03, 22},
+     {0, 0x04, 0},
+    },
+    /* 251 */
+    {
+     {3, 0x02, 127},
+     {6, 0x02, 127},
+     {10, 0x02, 127},
+     {15, 0x02, 127},
+     {24, 0x02, 127},
+     {31, 0x02, 127},
+     {41, 0x02, 127},
+     {56, 0x03, 127},
+     {3, 0x02, 220},
+     {6, 0x02, 220},
+     {10, 0x02, 220},
+     {15, 0x02, 220},
+     {24, 0x02, 220},
+     {31, 0x02, 220},
+     {41, 0x02, 220},
+     {56, 0x03, 220},
+    },
+    /* 252 */
+    {
+     {3, 0x02, 249},
+     {6, 0x02, 249},
+     {10, 0x02, 249},
+     {15, 0x02, 249},
+     {24, 0x02, 249},
+     {31, 0x02, 249},
+     {41, 0x02, 249},
+     {56, 0x03, 249},
+     {1, 0x02, 10},
+     {22, 0x03, 10},
+     {1, 0x02, 13},
+     {22, 0x03, 13},
+     {1, 0x02, 22},
+     {22, 0x03, 22},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+    },
+    /* 253 */
+    {
+     {2, 0x02, 10},
+     {9, 0x02, 10},
+     {23, 0x02, 10},
+     {40, 0x03, 10},
+     {2, 0x02, 13},
+     {9, 0x02, 13},
+     {23, 0x02, 13},
+     {40, 0x03, 13},
+     {2, 0x02, 22},
+     {9, 0x02, 22},
+     {23, 0x02, 22},
+     {40, 0x03, 22},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+    },
+    /* 254 */
+    {
+     {3, 0x02, 10},
+     {6, 0x02, 10},
+     {10, 0x02, 10},
+     {15, 0x02, 10},
+     {24, 0x02, 10},
+     {31, 0x02, 10},
+     {41, 0x02, 10},
+     {56, 0x03, 10},
+     {3, 0x02, 13},
+     {6, 0x02, 13},
+     {10, 0x02, 13},
+     {15, 0x02, 13},
+     {24, 0x02, 13},
+     {31, 0x02, 13},
+     {41, 0x02, 13},
+     {56, 0x03, 13},
+    },
+    /* 255 */
+    {
+     {3, 0x02, 22},
+     {6, 0x02, 22},
+     {10, 0x02, 22},
+     {15, 0x02, 22},
+     {24, 0x02, 22},
+     {31, 0x02, 22},
+     {41, 0x02, 22},
+     {56, 0x03, 22},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+     {0, 0x04, 0},
+    },
+};
diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c
new file mode 100644 (file)
index 0000000..4b59e63
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_helper.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include "nghttp2_net.h"
+
+void nghttp2_put_uint16be(uint8_t *buf, uint16_t n) {
+  uint16_t x = htons(n);
+  memcpy(buf, &x, sizeof(uint16_t));
+}
+
+void nghttp2_put_uint32be(uint8_t *buf, uint32_t n) {
+  uint32_t x = htonl(n);
+  memcpy(buf, &x, sizeof(uint32_t));
+}
+
+uint16_t nghttp2_get_uint16(const uint8_t *data) {
+  uint16_t n;
+  memcpy(&n, data, sizeof(uint16_t));
+  return ntohs(n);
+}
+
+uint32_t nghttp2_get_uint32(const uint8_t *data) {
+  uint32_t n;
+  memcpy(&n, data, sizeof(uint32_t));
+  return ntohl(n);
+}
+
+void *nghttp2_memdup(const void *src, size_t n, nghttp2_mem *mem) {
+  void *dest;
+
+  if (n == 0) {
+    return NULL;
+  }
+
+  dest = nghttp2_mem_malloc(mem, n);
+  if (dest == NULL) {
+    return NULL;
+  }
+  memcpy(dest, src, n);
+  return dest;
+}
+
+/* Generated by gendowncasetbl.py */
+static const int DOWNCASE_TBL[] = {
+    0 /* NUL  */,   1 /* SOH  */,   2 /* STX  */,   3 /* ETX  */,
+    4 /* EOT  */,   5 /* ENQ  */,   6 /* ACK  */,   7 /* BEL  */,
+    8 /* BS   */,   9 /* HT   */,   10 /* LF   */,  11 /* VT   */,
+    12 /* FF   */,  13 /* CR   */,  14 /* SO   */,  15 /* SI   */,
+    16 /* DLE  */,  17 /* DC1  */,  18 /* DC2  */,  19 /* DC3  */,
+    20 /* DC4  */,  21 /* NAK  */,  22 /* SYN  */,  23 /* ETB  */,
+    24 /* CAN  */,  25 /* EM   */,  26 /* SUB  */,  27 /* ESC  */,
+    28 /* FS   */,  29 /* GS   */,  30 /* RS   */,  31 /* US   */,
+    32 /* SPC  */,  33 /* !    */,  34 /* "    */,  35 /* #    */,
+    36 /* $    */,  37 /* %    */,  38 /* &    */,  39 /* '    */,
+    40 /* (    */,  41 /* )    */,  42 /* *    */,  43 /* +    */,
+    44 /* ,    */,  45 /* -    */,  46 /* .    */,  47 /* /    */,
+    48 /* 0    */,  49 /* 1    */,  50 /* 2    */,  51 /* 3    */,
+    52 /* 4    */,  53 /* 5    */,  54 /* 6    */,  55 /* 7    */,
+    56 /* 8    */,  57 /* 9    */,  58 /* :    */,  59 /* ;    */,
+    60 /* <    */,  61 /* =    */,  62 /* >    */,  63 /* ?    */,
+    64 /* @    */,  97 /* A    */,  98 /* B    */,  99 /* C    */,
+    100 /* D    */, 101 /* E    */, 102 /* F    */, 103 /* G    */,
+    104 /* H    */, 105 /* I    */, 106 /* J    */, 107 /* K    */,
+    108 /* L    */, 109 /* M    */, 110 /* N    */, 111 /* O    */,
+    112 /* P    */, 113 /* Q    */, 114 /* R    */, 115 /* S    */,
+    116 /* T    */, 117 /* U    */, 118 /* V    */, 119 /* W    */,
+    120 /* X    */, 121 /* Y    */, 122 /* Z    */, 91 /* [    */,
+    92 /* \    */,  93 /* ]    */,  94 /* ^    */,  95 /* _    */,
+    96 /* `    */,  97 /* a    */,  98 /* b    */,  99 /* c    */,
+    100 /* d    */, 101 /* e    */, 102 /* f    */, 103 /* g    */,
+    104 /* h    */, 105 /* i    */, 106 /* j    */, 107 /* k    */,
+    108 /* l    */, 109 /* m    */, 110 /* n    */, 111 /* o    */,
+    112 /* p    */, 113 /* q    */, 114 /* r    */, 115 /* s    */,
+    116 /* t    */, 117 /* u    */, 118 /* v    */, 119 /* w    */,
+    120 /* x    */, 121 /* y    */, 122 /* z    */, 123 /* {    */,
+    124 /* |    */, 125 /* }    */, 126 /* ~    */, 127 /* DEL  */,
+    128 /* 0x80 */, 129 /* 0x81 */, 130 /* 0x82 */, 131 /* 0x83 */,
+    132 /* 0x84 */, 133 /* 0x85 */, 134 /* 0x86 */, 135 /* 0x87 */,
+    136 /* 0x88 */, 137 /* 0x89 */, 138 /* 0x8a */, 139 /* 0x8b */,
+    140 /* 0x8c */, 141 /* 0x8d */, 142 /* 0x8e */, 143 /* 0x8f */,
+    144 /* 0x90 */, 145 /* 0x91 */, 146 /* 0x92 */, 147 /* 0x93 */,
+    148 /* 0x94 */, 149 /* 0x95 */, 150 /* 0x96 */, 151 /* 0x97 */,
+    152 /* 0x98 */, 153 /* 0x99 */, 154 /* 0x9a */, 155 /* 0x9b */,
+    156 /* 0x9c */, 157 /* 0x9d */, 158 /* 0x9e */, 159 /* 0x9f */,
+    160 /* 0xa0 */, 161 /* 0xa1 */, 162 /* 0xa2 */, 163 /* 0xa3 */,
+    164 /* 0xa4 */, 165 /* 0xa5 */, 166 /* 0xa6 */, 167 /* 0xa7 */,
+    168 /* 0xa8 */, 169 /* 0xa9 */, 170 /* 0xaa */, 171 /* 0xab */,
+    172 /* 0xac */, 173 /* 0xad */, 174 /* 0xae */, 175 /* 0xaf */,
+    176 /* 0xb0 */, 177 /* 0xb1 */, 178 /* 0xb2 */, 179 /* 0xb3 */,
+    180 /* 0xb4 */, 181 /* 0xb5 */, 182 /* 0xb6 */, 183 /* 0xb7 */,
+    184 /* 0xb8 */, 185 /* 0xb9 */, 186 /* 0xba */, 187 /* 0xbb */,
+    188 /* 0xbc */, 189 /* 0xbd */, 190 /* 0xbe */, 191 /* 0xbf */,
+    192 /* 0xc0 */, 193 /* 0xc1 */, 194 /* 0xc2 */, 195 /* 0xc3 */,
+    196 /* 0xc4 */, 197 /* 0xc5 */, 198 /* 0xc6 */, 199 /* 0xc7 */,
+    200 /* 0xc8 */, 201 /* 0xc9 */, 202 /* 0xca */, 203 /* 0xcb */,
+    204 /* 0xcc */, 205 /* 0xcd */, 206 /* 0xce */, 207 /* 0xcf */,
+    208 /* 0xd0 */, 209 /* 0xd1 */, 210 /* 0xd2 */, 211 /* 0xd3 */,
+    212 /* 0xd4 */, 213 /* 0xd5 */, 214 /* 0xd6 */, 215 /* 0xd7 */,
+    216 /* 0xd8 */, 217 /* 0xd9 */, 218 /* 0xda */, 219 /* 0xdb */,
+    220 /* 0xdc */, 221 /* 0xdd */, 222 /* 0xde */, 223 /* 0xdf */,
+    224 /* 0xe0 */, 225 /* 0xe1 */, 226 /* 0xe2 */, 227 /* 0xe3 */,
+    228 /* 0xe4 */, 229 /* 0xe5 */, 230 /* 0xe6 */, 231 /* 0xe7 */,
+    232 /* 0xe8 */, 233 /* 0xe9 */, 234 /* 0xea */, 235 /* 0xeb */,
+    236 /* 0xec */, 237 /* 0xed */, 238 /* 0xee */, 239 /* 0xef */,
+    240 /* 0xf0 */, 241 /* 0xf1 */, 242 /* 0xf2 */, 243 /* 0xf3 */,
+    244 /* 0xf4 */, 245 /* 0xf5 */, 246 /* 0xf6 */, 247 /* 0xf7 */,
+    248 /* 0xf8 */, 249 /* 0xf9 */, 250 /* 0xfa */, 251 /* 0xfb */,
+    252 /* 0xfc */, 253 /* 0xfd */, 254 /* 0xfe */, 255 /* 0xff */,
+};
+
+void nghttp2_downcase(uint8_t *s, size_t len) {
+  size_t i;
+  for (i = 0; i < len; ++i) {
+    s[i] = DOWNCASE_TBL[s[i]];
+  }
+}
+
+/*
+ *   local_window_size
+ *   ^  *
+ *   |  *    recv_window_size
+ *   |  *  * ^
+ *   |  *  * |
+ *  0+++++++++
+ *   |  *  *   \
+ *   |  *  *   | This rage is hidden in flow control.  But it must be
+ *   v  *  *   / kept in order to restore it when window size is enlarged.
+ *   recv_reduction
+ *   (+ for negative direction)
+ *
+ *   recv_window_size could be negative if we decrease
+ *   local_window_size more than recv_window_size:
+ *
+ *   local_window_size
+ *   ^  *
+ *   |  *
+ *   |  *
+ *   0++++++++
+ *   |  *    ^ recv_window_size (negative)
+ *   |  *    |
+ *   v  *  *
+ *   recv_reduction
+ */
+int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr,
+                                     int32_t *recv_window_size_ptr,
+                                     int32_t *recv_reduction_ptr,
+                                     int32_t *delta_ptr) {
+  if (*delta_ptr > 0) {
+    int32_t recv_reduction_delta;
+    int32_t delta;
+    int32_t new_recv_window_size =
+        nghttp2_max(0, *recv_window_size_ptr) - *delta_ptr;
+
+    if (new_recv_window_size >= 0) {
+      *recv_window_size_ptr = new_recv_window_size;
+      return 0;
+    }
+
+    delta = -new_recv_window_size;
+
+    /* The delta size is strictly more than received bytes. Increase
+       local_window_size by that difference |delta|. */
+    if (*local_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta) {
+      return NGHTTP2_ERR_FLOW_CONTROL;
+    }
+    *local_window_size_ptr += delta;
+    /* If there is recv_reduction due to earlier window_size
+       reduction, we have to adjust it too. */
+    recv_reduction_delta = nghttp2_min(*recv_reduction_ptr, delta);
+    *recv_reduction_ptr -= recv_reduction_delta;
+    if (*recv_window_size_ptr < 0) {
+      *recv_window_size_ptr += recv_reduction_delta;
+    } else {
+      /* If *recv_window_size_ptr > 0, then those bytes are going to
+         be returned to the remote peer (by WINDOW_UPDATE with the
+         adjusted *delta_ptr), so it is effectively 0 now.  We set to
+         *recv_reduction_delta, because caller does not take into
+         account it in *delta_ptr. */
+      *recv_window_size_ptr = recv_reduction_delta;
+    }
+    /* recv_reduction_delta must be paied from *delta_ptr, since it
+       was added in window size reduction (see below). */
+    *delta_ptr -= recv_reduction_delta;
+
+    return 0;
+  }
+
+  if (*local_window_size_ptr + *delta_ptr < 0 ||
+      *recv_window_size_ptr < INT32_MIN - *delta_ptr ||
+      *recv_reduction_ptr > INT32_MAX + *delta_ptr) {
+    return NGHTTP2_ERR_FLOW_CONTROL;
+  }
+  /* Decreasing local window size. Note that we achieve this without
+     noticing to the remote peer. To do this, we cut
+     recv_window_size by -delta. This means that we don't send
+     WINDOW_UPDATE for -delta bytes. */
+  *local_window_size_ptr += *delta_ptr;
+  *recv_window_size_ptr += *delta_ptr;
+  *recv_reduction_ptr -= *delta_ptr;
+  *delta_ptr = 0;
+
+  return 0;
+}
+
+int nghttp2_should_send_window_update(int32_t local_window_size,
+                                      int32_t recv_window_size) {
+  return recv_window_size >= local_window_size / 2;
+}
+
+const char *nghttp2_strerror(int error_code) {
+  switch (error_code) {
+  case 0:
+    return "Success";
+  case NGHTTP2_ERR_INVALID_ARGUMENT:
+    return "Invalid argument";
+  case NGHTTP2_ERR_BUFFER_ERROR:
+    return "Out of buffer space";
+  case NGHTTP2_ERR_UNSUPPORTED_VERSION:
+    return "Unsupported SPDY version";
+  case NGHTTP2_ERR_WOULDBLOCK:
+    return "Operation would block";
+  case NGHTTP2_ERR_PROTO:
+    return "Protocol error";
+  case NGHTTP2_ERR_INVALID_FRAME:
+    return "Invalid frame octets";
+  case NGHTTP2_ERR_EOF:
+    return "EOF";
+  case NGHTTP2_ERR_DEFERRED:
+    return "Data transfer deferred";
+  case NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE:
+    return "No more Stream ID available";
+  case NGHTTP2_ERR_STREAM_CLOSED:
+    return "Stream was already closed or invalid";
+  case NGHTTP2_ERR_STREAM_CLOSING:
+    return "Stream is closing";
+  case NGHTTP2_ERR_STREAM_SHUT_WR:
+    return "The transmission is not allowed for this stream";
+  case NGHTTP2_ERR_INVALID_STREAM_ID:
+    return "Stream ID is invalid";
+  case NGHTTP2_ERR_INVALID_STREAM_STATE:
+    return "Invalid stream state";
+  case NGHTTP2_ERR_DEFERRED_DATA_EXIST:
+    return "Another DATA frame has already been deferred";
+  case NGHTTP2_ERR_SESSION_CLOSING:
+    return "The current session is closing";
+  case NGHTTP2_ERR_START_STREAM_NOT_ALLOWED:
+    return "request HEADERS is not allowed";
+  case NGHTTP2_ERR_GOAWAY_ALREADY_SENT:
+    return "GOAWAY has already been sent";
+  case NGHTTP2_ERR_INVALID_HEADER_BLOCK:
+    return "Invalid header block";
+  case NGHTTP2_ERR_INVALID_STATE:
+    return "Invalid state";
+  case NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE:
+    return "The user callback function failed due to the temporal error";
+  case NGHTTP2_ERR_FRAME_SIZE_ERROR:
+    return "The length of the frame is invalid";
+  case NGHTTP2_ERR_HEADER_COMP:
+    return "Header compression/decompression error";
+  case NGHTTP2_ERR_FLOW_CONTROL:
+    return "Flow control error";
+  case NGHTTP2_ERR_INSUFF_BUFSIZE:
+    return "Insufficient buffer size given to function";
+  case NGHTTP2_ERR_PAUSE:
+    return "Callback was paused by the application";
+  case NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS:
+    return "Too many inflight SETTINGS";
+  case NGHTTP2_ERR_PUSH_DISABLED:
+    return "Server push is disabled by peer";
+  case NGHTTP2_ERR_DATA_EXIST:
+    return "DATA frame already exists";
+  case NGHTTP2_ERR_NOMEM:
+    return "Out of memory";
+  case NGHTTP2_ERR_CALLBACK_FAILURE:
+    return "The user callback function failed";
+  case NGHTTP2_ERR_BAD_PREFACE:
+    return "Received bad connection preface";
+  default:
+    return "Unknown error code";
+  }
+}
+
+/* Generated by gennmchartbl.py */
+static int VALID_HD_NAME_CHARS[] = {
+    0 /* NUL  */, 0 /* SOH  */, 0 /* STX  */, 0 /* ETX  */, 0 /* EOT  */,
+    0 /* ENQ  */, 0 /* ACK  */, 0 /* BEL  */, 0 /* BS   */, 0 /* HT   */,
+    0 /* LF   */, 0 /* VT   */, 0 /* FF   */, 0 /* CR   */, 0 /* SO   */,
+    0 /* SI   */, 0 /* DLE  */, 0 /* DC1  */, 0 /* DC2  */, 0 /* DC3  */,
+    0 /* DC4  */, 0 /* NAK  */, 0 /* SYN  */, 0 /* ETB  */, 0 /* CAN  */,
+    0 /* EM   */, 0 /* SUB  */, 0 /* ESC  */, 0 /* FS   */, 0 /* GS   */,
+    0 /* RS   */, 0 /* US   */, 0 /* SPC  */, 1 /* !    */, 0 /* "    */,
+    1 /* #    */, 1 /* $    */, 1 /* %    */, 1 /* &    */, 1 /* '    */,
+    0 /* (    */, 0 /* )    */, 1 /* *    */, 1 /* +    */, 0 /* ,    */,
+    1 /* -    */, 1 /* .    */, 0 /* /    */, 1 /* 0    */, 1 /* 1    */,
+    1 /* 2    */, 1 /* 3    */, 1 /* 4    */, 1 /* 5    */, 1 /* 6    */,
+    1 /* 7    */, 1 /* 8    */, 1 /* 9    */, 0 /* :    */, 0 /* ;    */,
+    0 /* <    */, 0 /* =    */, 0 /* >    */, 0 /* ?    */, 0 /* @    */,
+    0 /* A    */, 0 /* B    */, 0 /* C    */, 0 /* D    */, 0 /* E    */,
+    0 /* F    */, 0 /* G    */, 0 /* H    */, 0 /* I    */, 0 /* J    */,
+    0 /* K    */, 0 /* L    */, 0 /* M    */, 0 /* N    */, 0 /* O    */,
+    0 /* P    */, 0 /* Q    */, 0 /* R    */, 0 /* S    */, 0 /* T    */,
+    0 /* U    */, 0 /* V    */, 0 /* W    */, 0 /* X    */, 0 /* Y    */,
+    0 /* Z    */, 0 /* [    */, 0 /* \    */, 0 /* ]    */, 1 /* ^    */,
+    1 /* _    */, 1 /* `    */, 1 /* a    */, 1 /* b    */, 1 /* c    */,
+    1 /* d    */, 1 /* e    */, 1 /* f    */, 1 /* g    */, 1 /* h    */,
+    1 /* i    */, 1 /* j    */, 1 /* k    */, 1 /* l    */, 1 /* m    */,
+    1 /* n    */, 1 /* o    */, 1 /* p    */, 1 /* q    */, 1 /* r    */,
+    1 /* s    */, 1 /* t    */, 1 /* u    */, 1 /* v    */, 1 /* w    */,
+    1 /* x    */, 1 /* y    */, 1 /* z    */, 0 /* {    */, 1 /* |    */,
+    0 /* }    */, 1 /* ~    */, 0 /* DEL  */, 0 /* 0x80 */, 0 /* 0x81 */,
+    0 /* 0x82 */, 0 /* 0x83 */, 0 /* 0x84 */, 0 /* 0x85 */, 0 /* 0x86 */,
+    0 /* 0x87 */, 0 /* 0x88 */, 0 /* 0x89 */, 0 /* 0x8a */, 0 /* 0x8b */,
+    0 /* 0x8c */, 0 /* 0x8d */, 0 /* 0x8e */, 0 /* 0x8f */, 0 /* 0x90 */,
+    0 /* 0x91 */, 0 /* 0x92 */, 0 /* 0x93 */, 0 /* 0x94 */, 0 /* 0x95 */,
+    0 /* 0x96 */, 0 /* 0x97 */, 0 /* 0x98 */, 0 /* 0x99 */, 0 /* 0x9a */,
+    0 /* 0x9b */, 0 /* 0x9c */, 0 /* 0x9d */, 0 /* 0x9e */, 0 /* 0x9f */,
+    0 /* 0xa0 */, 0 /* 0xa1 */, 0 /* 0xa2 */, 0 /* 0xa3 */, 0 /* 0xa4 */,
+    0 /* 0xa5 */, 0 /* 0xa6 */, 0 /* 0xa7 */, 0 /* 0xa8 */, 0 /* 0xa9 */,
+    0 /* 0xaa */, 0 /* 0xab */, 0 /* 0xac */, 0 /* 0xad */, 0 /* 0xae */,
+    0 /* 0xaf */, 0 /* 0xb0 */, 0 /* 0xb1 */, 0 /* 0xb2 */, 0 /* 0xb3 */,
+    0 /* 0xb4 */, 0 /* 0xb5 */, 0 /* 0xb6 */, 0 /* 0xb7 */, 0 /* 0xb8 */,
+    0 /* 0xb9 */, 0 /* 0xba */, 0 /* 0xbb */, 0 /* 0xbc */, 0 /* 0xbd */,
+    0 /* 0xbe */, 0 /* 0xbf */, 0 /* 0xc0 */, 0 /* 0xc1 */, 0 /* 0xc2 */,
+    0 /* 0xc3 */, 0 /* 0xc4 */, 0 /* 0xc5 */, 0 /* 0xc6 */, 0 /* 0xc7 */,
+    0 /* 0xc8 */, 0 /* 0xc9 */, 0 /* 0xca */, 0 /* 0xcb */, 0 /* 0xcc */,
+    0 /* 0xcd */, 0 /* 0xce */, 0 /* 0xcf */, 0 /* 0xd0 */, 0 /* 0xd1 */,
+    0 /* 0xd2 */, 0 /* 0xd3 */, 0 /* 0xd4 */, 0 /* 0xd5 */, 0 /* 0xd6 */,
+    0 /* 0xd7 */, 0 /* 0xd8 */, 0 /* 0xd9 */, 0 /* 0xda */, 0 /* 0xdb */,
+    0 /* 0xdc */, 0 /* 0xdd */, 0 /* 0xde */, 0 /* 0xdf */, 0 /* 0xe0 */,
+    0 /* 0xe1 */, 0 /* 0xe2 */, 0 /* 0xe3 */, 0 /* 0xe4 */, 0 /* 0xe5 */,
+    0 /* 0xe6 */, 0 /* 0xe7 */, 0 /* 0xe8 */, 0 /* 0xe9 */, 0 /* 0xea */,
+    0 /* 0xeb */, 0 /* 0xec */, 0 /* 0xed */, 0 /* 0xee */, 0 /* 0xef */,
+    0 /* 0xf0 */, 0 /* 0xf1 */, 0 /* 0xf2 */, 0 /* 0xf3 */, 0 /* 0xf4 */,
+    0 /* 0xf5 */, 0 /* 0xf6 */, 0 /* 0xf7 */, 0 /* 0xf8 */, 0 /* 0xf9 */,
+    0 /* 0xfa */, 0 /* 0xfb */, 0 /* 0xfc */, 0 /* 0xfd */, 0 /* 0xfe */,
+    0 /* 0xff */
+};
+
+int nghttp2_check_header_name(const uint8_t *name, size_t len) {
+  const uint8_t *last;
+  if (len == 0) {
+    return 0;
+  }
+  if (*name == ':') {
+    if (len == 1) {
+      return 0;
+    }
+    ++name;
+    --len;
+  }
+  for (last = name + len; name != last; ++name) {
+    if (!VALID_HD_NAME_CHARS[*name]) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+/* Generated by genvchartbl.py */
+static int VALID_HD_VALUE_CHARS[] = {
+    0 /* NUL  */, 0 /* SOH  */, 0 /* STX  */, 0 /* ETX  */, 0 /* EOT  */,
+    0 /* ENQ  */, 0 /* ACK  */, 0 /* BEL  */, 0 /* BS   */, 1 /* HT   */,
+    0 /* LF   */, 0 /* VT   */, 0 /* FF   */, 0 /* CR   */, 0 /* SO   */,
+    0 /* SI   */, 0 /* DLE  */, 0 /* DC1  */, 0 /* DC2  */, 0 /* DC3  */,
+    0 /* DC4  */, 0 /* NAK  */, 0 /* SYN  */, 0 /* ETB  */, 0 /* CAN  */,
+    0 /* EM   */, 0 /* SUB  */, 0 /* ESC  */, 0 /* FS   */, 0 /* GS   */,
+    0 /* RS   */, 0 /* US   */, 1 /* SPC  */, 1 /* !    */, 1 /* "    */,
+    1 /* #    */, 1 /* $    */, 1 /* %    */, 1 /* &    */, 1 /* '    */,
+    1 /* (    */, 1 /* )    */, 1 /* *    */, 1 /* +    */, 1 /* ,    */,
+    1 /* -    */, 1 /* .    */, 1 /* /    */, 1 /* 0    */, 1 /* 1    */,
+    1 /* 2    */, 1 /* 3    */, 1 /* 4    */, 1 /* 5    */, 1 /* 6    */,
+    1 /* 7    */, 1 /* 8    */, 1 /* 9    */, 1 /* :    */, 1 /* ;    */,
+    1 /* <    */, 1 /* =    */, 1 /* >    */, 1 /* ?    */, 1 /* @    */,
+    1 /* A    */, 1 /* B    */, 1 /* C    */, 1 /* D    */, 1 /* E    */,
+    1 /* F    */, 1 /* G    */, 1 /* H    */, 1 /* I    */, 1 /* J    */,
+    1 /* K    */, 1 /* L    */, 1 /* M    */, 1 /* N    */, 1 /* O    */,
+    1 /* P    */, 1 /* Q    */, 1 /* R    */, 1 /* S    */, 1 /* T    */,
+    1 /* U    */, 1 /* V    */, 1 /* W    */, 1 /* X    */, 1 /* Y    */,
+    1 /* Z    */, 1 /* [    */, 1 /* \    */, 1 /* ]    */, 1 /* ^    */,
+    1 /* _    */, 1 /* `    */, 1 /* a    */, 1 /* b    */, 1 /* c    */,
+    1 /* d    */, 1 /* e    */, 1 /* f    */, 1 /* g    */, 1 /* h    */,
+    1 /* i    */, 1 /* j    */, 1 /* k    */, 1 /* l    */, 1 /* m    */,
+    1 /* n    */, 1 /* o    */, 1 /* p    */, 1 /* q    */, 1 /* r    */,
+    1 /* s    */, 1 /* t    */, 1 /* u    */, 1 /* v    */, 1 /* w    */,
+    1 /* x    */, 1 /* y    */, 1 /* z    */, 1 /* {    */, 1 /* |    */,
+    1 /* }    */, 1 /* ~    */, 0 /* DEL  */, 1 /* 0x80 */, 1 /* 0x81 */,
+    1 /* 0x82 */, 1 /* 0x83 */, 1 /* 0x84 */, 1 /* 0x85 */, 1 /* 0x86 */,
+    1 /* 0x87 */, 1 /* 0x88 */, 1 /* 0x89 */, 1 /* 0x8a */, 1 /* 0x8b */,
+    1 /* 0x8c */, 1 /* 0x8d */, 1 /* 0x8e */, 1 /* 0x8f */, 1 /* 0x90 */,
+    1 /* 0x91 */, 1 /* 0x92 */, 1 /* 0x93 */, 1 /* 0x94 */, 1 /* 0x95 */,
+    1 /* 0x96 */, 1 /* 0x97 */, 1 /* 0x98 */, 1 /* 0x99 */, 1 /* 0x9a */,
+    1 /* 0x9b */, 1 /* 0x9c */, 1 /* 0x9d */, 1 /* 0x9e */, 1 /* 0x9f */,
+    1 /* 0xa0 */, 1 /* 0xa1 */, 1 /* 0xa2 */, 1 /* 0xa3 */, 1 /* 0xa4 */,
+    1 /* 0xa5 */, 1 /* 0xa6 */, 1 /* 0xa7 */, 1 /* 0xa8 */, 1 /* 0xa9 */,
+    1 /* 0xaa */, 1 /* 0xab */, 1 /* 0xac */, 1 /* 0xad */, 1 /* 0xae */,
+    1 /* 0xaf */, 1 /* 0xb0 */, 1 /* 0xb1 */, 1 /* 0xb2 */, 1 /* 0xb3 */,
+    1 /* 0xb4 */, 1 /* 0xb5 */, 1 /* 0xb6 */, 1 /* 0xb7 */, 1 /* 0xb8 */,
+    1 /* 0xb9 */, 1 /* 0xba */, 1 /* 0xbb */, 1 /* 0xbc */, 1 /* 0xbd */,
+    1 /* 0xbe */, 1 /* 0xbf */, 1 /* 0xc0 */, 1 /* 0xc1 */, 1 /* 0xc2 */,
+    1 /* 0xc3 */, 1 /* 0xc4 */, 1 /* 0xc5 */, 1 /* 0xc6 */, 1 /* 0xc7 */,
+    1 /* 0xc8 */, 1 /* 0xc9 */, 1 /* 0xca */, 1 /* 0xcb */, 1 /* 0xcc */,
+    1 /* 0xcd */, 1 /* 0xce */, 1 /* 0xcf */, 1 /* 0xd0 */, 1 /* 0xd1 */,
+    1 /* 0xd2 */, 1 /* 0xd3 */, 1 /* 0xd4 */, 1 /* 0xd5 */, 1 /* 0xd6 */,
+    1 /* 0xd7 */, 1 /* 0xd8 */, 1 /* 0xd9 */, 1 /* 0xda */, 1 /* 0xdb */,
+    1 /* 0xdc */, 1 /* 0xdd */, 1 /* 0xde */, 1 /* 0xdf */, 1 /* 0xe0 */,
+    1 /* 0xe1 */, 1 /* 0xe2 */, 1 /* 0xe3 */, 1 /* 0xe4 */, 1 /* 0xe5 */,
+    1 /* 0xe6 */, 1 /* 0xe7 */, 1 /* 0xe8 */, 1 /* 0xe9 */, 1 /* 0xea */,
+    1 /* 0xeb */, 1 /* 0xec */, 1 /* 0xed */, 1 /* 0xee */, 1 /* 0xef */,
+    1 /* 0xf0 */, 1 /* 0xf1 */, 1 /* 0xf2 */, 1 /* 0xf3 */, 1 /* 0xf4 */,
+    1 /* 0xf5 */, 1 /* 0xf6 */, 1 /* 0xf7 */, 1 /* 0xf8 */, 1 /* 0xf9 */,
+    1 /* 0xfa */, 1 /* 0xfb */, 1 /* 0xfc */, 1 /* 0xfd */, 1 /* 0xfe */,
+    1 /* 0xff */
+};
+
+int nghttp2_check_header_value(const uint8_t *value, size_t len) {
+  const uint8_t *last;
+  for (last = value + len; value != last; ++value) {
+    if (!VALID_HD_VALUE_CHARS[*value]) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len) {
+  memcpy(dest, src, len);
+
+  return dest + len;
+}
diff --git a/lib/nghttp2_helper.h b/lib/nghttp2_helper.h
new file mode 100644 (file)
index 0000000..54422a8
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_HELPER_H
+#define NGHTTP2_HELPER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_mem.h"
+
+#define nghttp2_min(A, B) ((A) < (B) ? (A) : (B))
+#define nghttp2_max(A, B) ((A) > (B) ? (A) : (B))
+
+/*
+ * Copies 2 byte unsigned integer |n| in host byte order to |buf| in
+ * network byte order.
+ */
+void nghttp2_put_uint16be(uint8_t *buf, uint16_t n);
+
+/*
+ * Copies 4 byte unsigned integer |n| in host byte order to |buf| in
+ * network byte order.
+ */
+void nghttp2_put_uint32be(uint8_t *buf, uint32_t n);
+
+/*
+ * Retrieves 2 byte unsigned integer stored in |data| in network byte
+ * order and returns it in host byte order.
+ */
+uint16_t nghttp2_get_uint16(const uint8_t *data);
+
+/*
+ * Retrieves 4 byte unsigned integer stored in |data| in network byte
+ * order and returns it in host byte order.
+ */
+uint32_t nghttp2_get_uint32(const uint8_t *data);
+
+/*
+ * Allocates |n| bytes of memory and copy the memory region pointed by
+ * |src| with the length |n| bytes into it. Returns the allocated memory.
+ *
+ * This function returns pointer to allocated memory, or one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+void *nghttp2_memdup(const void *src, size_t n, nghttp2_mem *mem);
+
+void nghttp2_downcase(uint8_t *s, size_t len);
+
+/*
+ * Adjusts |*local_window_size_ptr|, |*recv_window_size_ptr|,
+ * |*recv_reduction_ptr| with |*delta_ptr| which is the
+ * WINDOW_UPDATE's window_size_increment sent from local side. If
+ * |delta| is strictly larger than |*recv_window_size_ptr|,
+ * |*local_window_size_ptr| is increased by delta -
+ * *recv_window_size_ptr. If |delta| is negative,
+ * |*local_window_size_ptr| is decreased by delta.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_FLOW_CONTROL
+ *     local_window_size overflow or gets negative.
+ */
+int nghttp2_adjust_local_window_size(int32_t *local_window_size_ptr,
+                                     int32_t *recv_window_size_ptr,
+                                     int32_t *recv_reduction_ptr,
+                                     int32_t *delta_ptr);
+
+/*
+ * Returns non-zero if the function decided that WINDOW_UPDATE should
+ * be sent.
+ */
+int nghttp2_should_send_window_update(int32_t local_window_size,
+                                      int32_t recv_window_size);
+
+/*
+ * Copies the buffer |src| of length |len| to the destination pointed
+ * by the |dest|, assuming that the |dest| is at lest |len| bytes long
+ * . Returns dest + len.
+ */
+uint8_t *nghttp2_cpymem(uint8_t *dest, const void *src, size_t len);
+
+#endif /* NGHTTP2_HELPER_H */
diff --git a/lib/nghttp2_int.h b/lib/nghttp2_int.h
new file mode 100644 (file)
index 0000000..effd667
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_INT_H
+#define NGHTTP2_INT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+/* Macros, types and constants for internal use */
+
+#ifdef DEBUGBUILD
+#define DEBUGF(x) x
+#else
+#define DEBUGF(x)                                                              \
+  do {                                                                         \
+  } while (0)
+#endif
+
+typedef int (*nghttp2_compar)(const void *lhs, const void *rhs);
+
+/* Internal error code. They must be in the range [-499, -100],
+   inclusive. */
+typedef enum {
+  NGHTTP2_ERR_CREDENTIAL_PENDING = -101,
+  NGHTTP2_ERR_IGN_HEADER_BLOCK = -103,
+  NGHTTP2_ERR_IGN_PAYLOAD = -104
+} nghttp2_internal_error;
+
+#endif /* NGHTTP2_INT_H */
diff --git a/lib/nghttp2_map.c b/lib/nghttp2_map.c
new file mode 100644 (file)
index 0000000..5a38ee2
--- /dev/null
@@ -0,0 +1,190 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_map.h"
+
+#include <string.h>
+
+#define INITIAL_TABLE_LENGTH 256
+
+int nghttp2_map_init(nghttp2_map *map, nghttp2_mem *mem) {
+  map->mem = mem;
+  map->tablelen = INITIAL_TABLE_LENGTH;
+  map->table =
+      nghttp2_mem_calloc(mem, map->tablelen, sizeof(nghttp2_map_entry *));
+  if (map->table == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  map->size = 0;
+
+  return 0;
+}
+
+void nghttp2_map_free(nghttp2_map *map) {
+  nghttp2_mem_free(map->mem, map->table);
+}
+
+void nghttp2_map_each_free(nghttp2_map *map,
+                           int (*func)(nghttp2_map_entry *entry, void *ptr),
+                           void *ptr) {
+  size_t i;
+  for (i = 0; i < map->tablelen; ++i) {
+    nghttp2_map_entry *entry;
+    for (entry = map->table[i]; entry;) {
+      nghttp2_map_entry *next = entry->next;
+      func(entry, ptr);
+      entry = next;
+    }
+    map->table[i] = NULL;
+  }
+}
+
+int nghttp2_map_each(nghttp2_map *map,
+                     int (*func)(nghttp2_map_entry *entry, void *ptr),
+                     void *ptr) {
+  int rv;
+  size_t i;
+  for (i = 0; i < map->tablelen; ++i) {
+    nghttp2_map_entry *entry;
+    for (entry = map->table[i]; entry; entry = entry->next) {
+      rv = func(entry, ptr);
+      if (rv != 0) {
+        return rv;
+      }
+    }
+  }
+  return 0;
+}
+
+void nghttp2_map_entry_init(nghttp2_map_entry *entry, key_type key) {
+  entry->key = key;
+  entry->next = NULL;
+}
+
+/* Same hash function in android HashMap source code. */
+/* The |mod| must be power of 2 */
+static int32_t hash(int32_t h, size_t mod) {
+  h ^= (h >> 20) ^ (h >> 12);
+  h ^= (h >> 7) ^ (h >> 4);
+  return h & (mod - 1);
+}
+
+static int insert(nghttp2_map_entry **table, size_t tablelen,
+                  nghttp2_map_entry *entry) {
+  int32_t h = hash(entry->key, tablelen);
+  if (table[h] == NULL) {
+    table[h] = entry;
+  } else {
+    nghttp2_map_entry *p;
+    /* We won't allow duplicated key, so check it out. */
+    for (p = table[h]; p; p = p->next) {
+      if (p->key == entry->key) {
+        return NGHTTP2_ERR_INVALID_ARGUMENT;
+      }
+    }
+    entry->next = table[h];
+    table[h] = entry;
+  }
+  return 0;
+}
+
+/* new_tablelen must be power of 2 */
+static int resize(nghttp2_map *map, size_t new_tablelen) {
+  size_t i;
+  nghttp2_map_entry **new_table;
+
+  new_table =
+      nghttp2_mem_calloc(map->mem, new_tablelen, sizeof(nghttp2_map_entry *));
+  if (new_table == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  for (i = 0; i < map->tablelen; ++i) {
+    nghttp2_map_entry *entry;
+    for (entry = map->table[i]; entry;) {
+      nghttp2_map_entry *next = entry->next;
+      entry->next = NULL;
+      /* This function must succeed */
+      insert(new_table, new_tablelen, entry);
+      entry = next;
+    }
+  }
+  nghttp2_mem_free(map->mem, map->table);
+  map->tablelen = new_tablelen;
+  map->table = new_table;
+
+  return 0;
+}
+
+int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_entry *new_entry) {
+  int rv;
+  /* Load factor is 0.75 */
+  if ((map->size + 1) * 4 > map->tablelen * 3) {
+    rv = resize(map, map->tablelen * 2);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+  rv = insert(map->table, map->tablelen, new_entry);
+  if (rv != 0) {
+    return rv;
+  }
+  ++map->size;
+  return 0;
+}
+
+nghttp2_map_entry *nghttp2_map_find(nghttp2_map *map, key_type key) {
+  int32_t h;
+  nghttp2_map_entry *entry;
+  h = hash(key, map->tablelen);
+  for (entry = map->table[h]; entry; entry = entry->next) {
+    if (entry->key == key) {
+      return entry;
+    }
+  }
+  return NULL;
+}
+
+int nghttp2_map_remove(nghttp2_map *map, key_type key) {
+  int32_t h;
+  nghttp2_map_entry *entry, *prev;
+  h = hash(key, map->tablelen);
+  prev = NULL;
+  for (entry = map->table[h]; entry; entry = entry->next) {
+    if (entry->key == key) {
+      if (prev == NULL) {
+        map->table[h] = entry->next;
+      } else {
+        prev->next = entry->next;
+      }
+      --map->size;
+      return 0;
+    }
+    prev = entry;
+  }
+  return NGHTTP2_ERR_INVALID_ARGUMENT;
+}
+
+size_t nghttp2_map_size(nghttp2_map *map) { return map->size; }
diff --git a/lib/nghttp2_map.h b/lib/nghttp2_map.h
new file mode 100644 (file)
index 0000000..8774427
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_MAP_H
+#define NGHTTP2_MAP_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_int.h"
+#include "nghttp2_mem.h"
+
+/* Implementation of unordered map */
+
+typedef uint32_t key_type;
+
+typedef struct nghttp2_map_entry {
+  struct nghttp2_map_entry *next;
+  key_type key;
+} nghttp2_map_entry;
+
+typedef struct {
+  nghttp2_map_entry **table;
+  nghttp2_mem *mem;
+  size_t tablelen;
+  size_t size;
+} nghttp2_map;
+
+/*
+ * Initializes the map |map|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ */
+int nghttp2_map_init(nghttp2_map *map, nghttp2_mem *mem);
+
+/*
+ * Deallocates any resources allocated for |map|. The stored entries
+ * are not freed by this function. Use nghttp2_map_each_free() to free
+ * each entries.
+ */
+void nghttp2_map_free(nghttp2_map *map);
+
+/*
+ * Deallocates each entries using |func| function and any resources
+ * allocated for |map|. The |func| function is responsible for freeing
+ * given the |entry| object. The |ptr| will be passed to the |func| as
+ * send argument. The return value of the |func| will be ignored.
+ */
+void nghttp2_map_each_free(nghttp2_map *map,
+                           int (*func)(nghttp2_map_entry *entry, void *ptr),
+                           void *ptr);
+
+/*
+ * Initializes the |entry| with the |key|. All entries to be inserted
+ * to the map must be initialized with this function.
+ */
+void nghttp2_map_entry_init(nghttp2_map_entry *entry, key_type key);
+
+/*
+ * Inserts the new |entry| with the key |entry->key| to the map |map|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     The item associated by |key| already exists.
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory
+ */
+int nghttp2_map_insert(nghttp2_map *map, nghttp2_map_entry *entry);
+
+/*
+ * Returns the entry associated by the key |key|.  If there is no such
+ * entry, this function returns NULL.
+ */
+nghttp2_map_entry *nghttp2_map_find(nghttp2_map *map, key_type key);
+
+/*
+ * Removes the entry associated by the key |key| from the |map|.  The
+ * removed entry is not freed by this function.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     The entry associated by |key| does not exist.
+ */
+int nghttp2_map_remove(nghttp2_map *map, key_type key);
+
+/*
+ * Returns the number of items stored in the map |map|.
+ */
+size_t nghttp2_map_size(nghttp2_map *map);
+
+/*
+ * Applies the function |func| to each entry in the |map| with the
+ * optional user supplied pointer |ptr|.
+ *
+ * If the |func| returns 0, this function calls the |func| with the
+ * next entry. If the |func| returns nonzero, it will not call the
+ * |func| for further entries and return the return value of the
+ * |func| immediately.  Thus, this function returns 0 if all the
+ * invocations of the |func| return 0, or nonzero value which the last
+ * invocation of |func| returns.
+ *
+ * Don't use this function to free each entry. Use
+ * nghttp2_map_each_free() instead.
+ */
+int nghttp2_map_each(nghttp2_map *map,
+                     int (*func)(nghttp2_map_entry *entry, void *ptr),
+                     void *ptr);
+
+#endif /* NGHTTP2_MAP_H */
diff --git a/lib/nghttp2_mem.c b/lib/nghttp2_mem.c
new file mode 100644 (file)
index 0000000..e7d5aae
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_mem.h"
+
+static void *default_malloc(size_t size, void *mem_user_data _U_) {
+  return malloc(size);
+}
+
+static void default_free(void *ptr, void *mem_user_data _U_) { free(ptr); }
+
+static void *default_calloc(size_t nmemb, size_t size,
+                            void *mem_user_data _U_) {
+  return calloc(nmemb, size);
+}
+
+static void *default_realloc(void *ptr, size_t size, void *mem_user_data _U_) {
+  return realloc(ptr, size);
+}
+
+static nghttp2_mem mem_default = {NULL, default_malloc, default_free,
+                                  default_calloc, default_realloc};
+
+nghttp2_mem *nghttp2_mem_default(void) { return &mem_default; }
+
+void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size) {
+  return mem->malloc(size, mem->mem_user_data);
+}
+
+void nghttp2_mem_free(nghttp2_mem *mem, void *ptr) {
+  mem->free(ptr, mem->mem_user_data);
+}
+
+void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size) {
+  return mem->calloc(nmemb, size, mem->mem_user_data);
+}
+
+void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size) {
+  return mem->realloc(ptr, size, mem->mem_user_data);
+}
diff --git a/lib/nghttp2_mem.h b/lib/nghttp2_mem.h
new file mode 100644 (file)
index 0000000..d1fded4
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_MEM_H
+#define NGHTTP2_MEM_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+/* The default, system standard memory allocator */
+nghttp2_mem *nghttp2_mem_default(void);
+
+/* Convenient wrapper functions to call allocator function in
+   |mem|. */
+void *nghttp2_mem_malloc(nghttp2_mem *mem, size_t size);
+void nghttp2_mem_free(nghttp2_mem *mem, void *ptr);
+void *nghttp2_mem_calloc(nghttp2_mem *mem, size_t nmemb, size_t size);
+void *nghttp2_mem_realloc(nghttp2_mem *mem, void *ptr, size_t size);
+
+#endif /* NGHTTP2_MEM_H */
diff --git a/lib/nghttp2_net.h b/lib/nghttp2_net.h
new file mode 100644 (file)
index 0000000..3f82adf
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_NET_H
+#define NGHTTP2_NET_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif /* HAVE_ARPA_INET_H */
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+
+#ifdef HAVE_WINSOCK2_H
+#include <winsock2.h>
+#endif /* HAVE_WINSOCK2_H */
+
+#endif /* NGHTTP2_NET_H */
diff --git a/lib/nghttp2_npn.c b/lib/nghttp2_npn.c
new file mode 100644 (file)
index 0000000..a8bdb23
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_npn.h"
+
+#include <string.h>
+
+static int select_next_protocol(unsigned char **out, unsigned char *outlen,
+                                const unsigned char *in, unsigned int inlen,
+                                const char *key, unsigned int keylen) {
+  unsigned int i;
+  for (i = 0; i + keylen <= inlen; i += in [i] + 1) {
+    if (memcmp(&in[i], key, keylen) == 0) {
+      *out = (unsigned char *)&in[i + 1];
+      *outlen = in[i];
+      return 0;
+    }
+  }
+  return -1;
+}
+
+#define NGHTTP2_HTTP_1_1_ALPN "\x8http/1.1"
+#define NGHTTP2_HTTP_1_1_ALPN_LEN (sizeof(NGHTTP2_HTTP_1_1_ALPN) - 1)
+
+int nghttp2_select_next_protocol(unsigned char **out, unsigned char *outlen,
+                                 const unsigned char *in, unsigned int inlen) {
+  if (select_next_protocol(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN,
+                           NGHTTP2_PROTO_ALPN_LEN) == 0) {
+    return 1;
+  }
+  if (select_next_protocol(out, outlen, in, inlen, NGHTTP2_HTTP_1_1_ALPN,
+                           NGHTTP2_HTTP_1_1_ALPN_LEN) == 0) {
+    return 0;
+  }
+  return -1;
+}
diff --git a/lib/nghttp2_npn.h b/lib/nghttp2_npn.h
new file mode 100644 (file)
index 0000000..c4bdedb
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_NPN_H
+#define NGHTTP2_NPN_H
+
+#ifdef HAVE_CONFIG
+#include <config.h>
+#endif /* HAVE_CONFIG */
+
+#include <nghttp2/nghttp2.h>
+
+#endif /* NGHTTP2_NPN_H */
diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c
new file mode 100644 (file)
index 0000000..3acb0b9
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_option.h"
+
+int nghttp2_option_new(nghttp2_option **option_ptr) {
+  *option_ptr = calloc(1, sizeof(nghttp2_option));
+
+  if (*option_ptr == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  return 0;
+}
+
+void nghttp2_option_del(nghttp2_option *option) { free(option); }
+
+void nghttp2_option_set_no_auto_window_update(nghttp2_option *option, int val) {
+  option->opt_set_mask |= NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE;
+  option->no_auto_window_update = val;
+}
+
+void nghttp2_option_set_peer_max_concurrent_streams(nghttp2_option *option,
+                                                    uint32_t val) {
+  option->opt_set_mask |= NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS;
+  option->peer_max_concurrent_streams = val;
+}
+
+void nghttp2_option_set_recv_client_preface(nghttp2_option *option, int val) {
+  option->opt_set_mask |= NGHTTP2_OPT_RECV_CLIENT_PREFACE;
+  option->recv_client_preface = val;
+}
diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h
new file mode 100644 (file)
index 0000000..c9f2d92
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_OPTION_H
+#define NGHTTP2_OPTION_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+/**
+ * Configuration options
+ */
+typedef enum {
+  /**
+   * This option prevents the library from sending WINDOW_UPDATE for a
+   * connection automatically.  If this option is set to nonzero, the
+   * library won't send WINDOW_UPDATE for DATA until application calls
+   * nghttp2_session_consume() to indicate the amount of consumed
+   * DATA.  By default, this option is set to zero.
+   */
+  NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE = 1,
+  /**
+   * This option sets the SETTINGS_MAX_CONCURRENT_STREAMS value of
+   * remote endpoint as if it is received in SETTINGS frame. Without
+   * specifying this option, before the local endpoint receives
+   * SETTINGS_MAX_CONCURRENT_STREAMS in SETTINGS frame from remote
+   * endpoint, SETTINGS_MAX_CONCURRENT_STREAMS is unlimited. This may
+   * cause problem if local endpoint submits lots of requests
+   * initially and sending them at once to the remote peer may lead to
+   * the rejection of some requests. Specifying this option to the
+   * sensible value, say 100, may avoid this kind of issue. This value
+   * will be overwritten if the local endpoint receives
+   * SETTINGS_MAX_CONCURRENT_STREAMS from the remote endpoint.
+   */
+  NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1,
+  NGHTTP2_OPT_RECV_CLIENT_PREFACE = 1 << 2,
+} nghttp2_option_flag;
+
+/**
+ * Struct to store option values for nghttp2_session.
+ */
+struct nghttp2_option {
+  /**
+   * Bitwise OR of nghttp2_option_flag to determine that which fields
+   * are specified.
+   */
+  uint32_t opt_set_mask;
+  /**
+   * NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS
+   */
+  uint32_t peer_max_concurrent_streams;
+  /**
+   * NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE
+   */
+  uint8_t no_auto_window_update;
+  /**
+   * NGHTTP2_OPT_RECV_CLIENT_PREFACE
+   */
+  uint8_t recv_client_preface;
+};
+
+#endif /* NGHTTP2_OPTION_H */
diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c
new file mode 100644 (file)
index 0000000..2aaff37
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_outbound_item.h"
+
+#include <assert.h>
+
+void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) {
+  nghttp2_frame *frame;
+
+  if (item == NULL) {
+    return;
+  }
+
+  frame = &item->frame;
+
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA:
+    nghttp2_frame_data_free(&frame->data);
+    break;
+  case NGHTTP2_HEADERS:
+    nghttp2_frame_headers_free(&frame->headers, mem);
+    break;
+  case NGHTTP2_PRIORITY:
+    nghttp2_frame_priority_free(&frame->priority);
+    break;
+  case NGHTTP2_RST_STREAM:
+    nghttp2_frame_rst_stream_free(&frame->rst_stream);
+    break;
+  case NGHTTP2_SETTINGS:
+    nghttp2_frame_settings_free(&frame->settings, mem);
+    break;
+  case NGHTTP2_PUSH_PROMISE:
+    nghttp2_frame_push_promise_free(&frame->push_promise, mem);
+    break;
+  case NGHTTP2_PING:
+    nghttp2_frame_ping_free(&frame->ping);
+    break;
+  case NGHTTP2_GOAWAY:
+    nghttp2_frame_goaway_free(&frame->goaway, mem);
+    break;
+  case NGHTTP2_WINDOW_UPDATE:
+    nghttp2_frame_window_update_free(&frame->window_update);
+    break;
+  }
+}
diff --git a/lib/nghttp2_outbound_item.h b/lib/nghttp2_outbound_item.h
new file mode 100644 (file)
index 0000000..e52866a
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_OUTBOUND_ITEM_H
+#define NGHTTP2_OUTBOUND_ITEM_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_frame.h"
+#include "nghttp2_mem.h"
+
+/* A bit higher weight for non-DATA frames */
+#define NGHTTP2_OB_EX_WEIGHT 300
+/* Higher weight for SETTINGS */
+#define NGHTTP2_OB_SETTINGS_WEIGHT 301
+/* Highest weight for PING */
+#define NGHTTP2_OB_PING_WEIGHT 302
+
+/* struct used for HEADERS and PUSH_PROMISE frame */
+typedef struct {
+  nghttp2_data_provider data_prd;
+  void *stream_user_data;
+  /* nonzero if this item should be attached to stream object to make
+     it under priority control */
+  uint8_t attach_stream;
+} nghttp2_headers_aux_data;
+
+/* struct used for DATA frame */
+typedef struct {
+  /**
+   * The data to be sent for this DATA frame.
+   */
+  nghttp2_data_provider data_prd;
+  /**
+   * The flags of DATA frame.  We use separate flags here and
+   * nghttp2_data frame.  The latter contains flags actually sent to
+   * peer.  This |flags| may contain NGHTTP2_FLAG_END_STREAM and only
+   * when |eof| becomes nonzero, flags in nghttp2_data has
+   * NGHTTP2_FLAG_END_STREAM set.
+   */
+  uint8_t flags;
+  /**
+   * The flag to indicate whether EOF was reached or not. Initially
+   * |eof| is 0. It becomes 1 after all data were read.
+   */
+  uint8_t eof;
+} nghttp2_data_aux_data;
+
+typedef enum {
+  NGHTTP2_GOAWAY_AUX_NONE = 0x0,
+  /* indicates that session should be terminated after the
+     transmission of this frame. */
+  NGHTTP2_GOAWAY_AUX_TERM_ON_SEND = 0x1,
+  /* indicates that this GOAWAY is just a notification for graceful
+     shutdown.  No nghttp2_session.goaway_flags should be updated on
+     the reaction to this frame. */
+  NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE = 0x2,
+} nghttp2_goaway_aux_flag;
+
+/* struct used for GOAWAY frame */
+typedef struct {
+  /* bitwise-OR of one or more of nghttp2_goaway_aux_flag. */
+  uint8_t flags;
+} nghttp2_goaway_aux_data;
+
+/* Additional data which cannot be stored in nghttp2_frame struct */
+typedef union {
+  nghttp2_data_aux_data data;
+  nghttp2_headers_aux_data headers;
+  nghttp2_goaway_aux_data goaway;
+} nghttp2_aux_data;
+
+typedef struct {
+  nghttp2_frame frame;
+  nghttp2_aux_data aux_data;
+  int64_t seq;
+  /* Reset count of weight. See comment for last_cycle in
+     nghttp2_session.h */
+  uint64_t cycle;
+  /* The priority used in priority comparion.  Larger is served
+     ealier. */
+  int32_t weight;
+  /* nonzero if this object is queued. */
+  uint8_t queued;
+} nghttp2_outbound_item;
+
+/*
+ * Deallocates resource for |item|. If |item| is NULL, this function
+ * does nothing.
+ */
+void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem);
+
+#endif /* NGHTTP2_OUTBOUND_ITEM_H */
diff --git a/lib/nghttp2_pq.c b/lib/nghttp2_pq.c
new file mode 100644 (file)
index 0000000..4ad37c0
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_pq.h"
+
+int nghttp2_pq_init(nghttp2_pq *pq, nghttp2_compar compar, nghttp2_mem *mem) {
+  pq->mem = mem;
+  pq->capacity = 128;
+  pq->q = nghttp2_mem_malloc(mem, pq->capacity * sizeof(void *));
+  if (pq->q == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+  pq->length = 0;
+  pq->compar = compar;
+  return 0;
+}
+
+void nghttp2_pq_free(nghttp2_pq *pq) {
+  nghttp2_mem_free(pq->mem, pq->q);
+  pq->q = NULL;
+}
+
+static void swap(nghttp2_pq *pq, size_t i, size_t j) {
+  void *t = pq->q[i];
+  pq->q[i] = pq->q[j];
+  pq->q[j] = t;
+}
+
+static void bubble_up(nghttp2_pq *pq, size_t index) {
+  if (index == 0) {
+    return;
+  } else {
+    size_t parent = (index - 1) / 2;
+    if (pq->compar(pq->q[parent], pq->q[index]) > 0) {
+      swap(pq, parent, index);
+      bubble_up(pq, parent);
+    }
+  }
+}
+
+int nghttp2_pq_push(nghttp2_pq *pq, void *item) {
+  if (pq->capacity <= pq->length) {
+    void *nq;
+    nq = nghttp2_mem_realloc(pq->mem, pq->q,
+                             (pq->capacity * 2) * sizeof(void *));
+    if (nq == NULL) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+    pq->capacity *= 2;
+    pq->q = nq;
+  }
+  pq->q[pq->length] = item;
+  ++pq->length;
+  bubble_up(pq, pq->length - 1);
+  return 0;
+}
+
+void *nghttp2_pq_top(nghttp2_pq *pq) {
+  if (pq->length == 0) {
+    return NULL;
+  } else {
+    return pq->q[0];
+  }
+}
+
+static void bubble_down(nghttp2_pq *pq, size_t index) {
+  size_t lchild = index * 2 + 1;
+  size_t minindex = index;
+  size_t i, j;
+  for (i = 0; i < 2; ++i) {
+    j = lchild + i;
+    if (j >= pq->length) {
+      break;
+    }
+    if (pq->compar(pq->q[minindex], pq->q[j]) > 0) {
+      minindex = j;
+    }
+  }
+  if (minindex != index) {
+    swap(pq, index, minindex);
+    bubble_down(pq, minindex);
+  }
+}
+
+void nghttp2_pq_pop(nghttp2_pq *pq) {
+  if (pq->length > 0) {
+    pq->q[0] = pq->q[pq->length - 1];
+    --pq->length;
+    bubble_down(pq, 0);
+  }
+}
+
+int nghttp2_pq_empty(nghttp2_pq *pq) { return pq->length == 0; }
+
+size_t nghttp2_pq_size(nghttp2_pq *pq) { return pq->length; }
+
+void nghttp2_pq_update(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg) {
+  size_t i;
+  int rv = 0;
+  if (pq->length == 0) {
+    return;
+  }
+  for (i = 0; i < pq->length; ++i) {
+    rv |= (*fun)(pq->q[i], arg);
+  }
+  if (rv) {
+    for (i = pq->length; i > 0; --i) {
+      bubble_down(pq, i - 1);
+    }
+  }
+}
diff --git a/lib/nghttp2_pq.h b/lib/nghttp2_pq.h
new file mode 100644 (file)
index 0000000..e23dd49
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_PQ_H
+#define NGHTTP2_PQ_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_int.h"
+#include "nghttp2_mem.h"
+
+/* Implementation of priority queue */
+
+typedef struct {
+  /* The pointer to the pointer to the item stored */
+  void **q;
+  /* Memory allocator */
+  nghttp2_mem *mem;
+  /* The number of items sotred */
+  size_t length;
+  /* The maximum number of items this pq can store. This is
+     automatically extended when length is reached to this value. */
+  size_t capacity;
+  /* The compare function between items */
+  nghttp2_compar compar;
+} nghttp2_pq;
+
+/*
+ * Initializes priority queue |pq| with compare function |cmp|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_pq_init(nghttp2_pq *pq, nghttp2_compar cmp, nghttp2_mem *mem);
+
+/*
+ * Deallocates any resources allocated for |pq|.  The stored items are
+ * not freed by this function.
+ */
+void nghttp2_pq_free(nghttp2_pq *pq);
+
+/*
+ * Adds |item| to the priority queue |pq|.
+ *
+ * This function returns 0 if it succeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_pq_push(nghttp2_pq *pq, void *item);
+
+/*
+ * Returns item at the top of the queue |pq|. If the queue is empty,
+ * this function returns NULL.
+ */
+void *nghttp2_pq_top(nghttp2_pq *pq);
+
+/*
+ * Pops item at the top of the queue |pq|. The popped item is not
+ * freed by this function.
+ */
+void nghttp2_pq_pop(nghttp2_pq *pq);
+
+/*
+ * Returns nonzero if the queue |pq| is empty.
+ */
+int nghttp2_pq_empty(nghttp2_pq *pq);
+
+/*
+ * Returns the number of items in the queue |pq|.
+ */
+size_t nghttp2_pq_size(nghttp2_pq *pq);
+
+typedef int (*nghttp2_pq_item_cb)(void *item, void *arg);
+
+/*
+ * Updates each item in |pq| using function |fun| and re-construct
+ * priority queue. The |fun| must return non-zero if it modifies the
+ * item in a way that it affects ordering in the priority queue. The
+ * |arg| is passed to the 2nd parameter of |fun|.
+ */
+void nghttp2_pq_update(nghttp2_pq *pq, nghttp2_pq_item_cb fun, void *arg);
+
+#endif /* NGHTTP2_PQ_H */
diff --git a/lib/nghttp2_priority_spec.c b/lib/nghttp2_priority_spec.c
new file mode 100644 (file)
index 0000000..cd254b1
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_priority_spec.h"
+
+void nghttp2_priority_spec_init(nghttp2_priority_spec *pri_spec,
+                                int32_t stream_id, int32_t weight,
+                                int exclusive) {
+  pri_spec->stream_id = stream_id;
+  pri_spec->weight = weight;
+  pri_spec->exclusive = exclusive != 0;
+}
+
+void nghttp2_priority_spec_default_init(nghttp2_priority_spec *pri_spec) {
+  pri_spec->stream_id = 0;
+  pri_spec->weight = NGHTTP2_DEFAULT_WEIGHT;
+  pri_spec->exclusive = 0;
+}
+
+int nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec) {
+  return pri_spec->stream_id == 0 &&
+         pri_spec->weight == NGHTTP2_DEFAULT_WEIGHT && pri_spec->exclusive == 0;
+}
diff --git a/lib/nghttp2_priority_spec.h b/lib/nghttp2_priority_spec.h
new file mode 100644 (file)
index 0000000..a325a58
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_PRIORITY_SPEC_H
+#define NGHTTP2_PRIORITY_SPEC_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+#endif /* NGHTTP2_PRIORITY_SPEC_H */
diff --git a/lib/nghttp2_queue.c b/lib/nghttp2_queue.c
new file mode 100644 (file)
index 0000000..055eb69
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_queue.h"
+
+#include <string.h>
+#include <assert.h>
+
+void nghttp2_queue_init(nghttp2_queue *queue) {
+  queue->front = queue->back = NULL;
+}
+
+void nghttp2_queue_free(nghttp2_queue *queue) {
+  if (!queue) {
+    return;
+  } else {
+    nghttp2_queue_cell *p = queue->front;
+    while (p) {
+      nghttp2_queue_cell *next = p->next;
+      free(p);
+      p = next;
+    }
+  }
+}
+
+int nghttp2_queue_push(nghttp2_queue *queue, void *data) {
+  nghttp2_queue_cell *new_cell =
+      (nghttp2_queue_cell *)malloc(sizeof(nghttp2_queue_cell));
+  if (!new_cell) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+  new_cell->data = data;
+  new_cell->next = NULL;
+  if (queue->back) {
+    queue->back->next = new_cell;
+    queue->back = new_cell;
+
+  } else {
+    queue->front = queue->back = new_cell;
+  }
+  return 0;
+}
+
+void nghttp2_queue_pop(nghttp2_queue *queue) {
+  nghttp2_queue_cell *front = queue->front;
+  assert(front);
+  queue->front = front->next;
+  if (front == queue->back) {
+    queue->back = NULL;
+  }
+  free(front);
+}
+
+void *nghttp2_queue_front(nghttp2_queue *queue) {
+  assert(queue->front);
+  return queue->front->data;
+}
+
+void *nghttp2_queue_back(nghttp2_queue *queue) {
+  assert(queue->back);
+  return queue->back->data;
+}
+
+int nghttp2_queue_empty(nghttp2_queue *queue) { return queue->front == NULL; }
diff --git a/lib/nghttp2_queue.h b/lib/nghttp2_queue.h
new file mode 100644 (file)
index 0000000..d872b07
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_QUEUE_H
+#define NGHTTP2_QUEUE_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+typedef struct nghttp2_queue_cell {
+  void *data;
+  struct nghttp2_queue_cell *next;
+} nghttp2_queue_cell;
+
+typedef struct { nghttp2_queue_cell *front, *back; } nghttp2_queue;
+
+void nghttp2_queue_init(nghttp2_queue *queue);
+void nghttp2_queue_free(nghttp2_queue *queue);
+int nghttp2_queue_push(nghttp2_queue *queue, void *data);
+void nghttp2_queue_pop(nghttp2_queue *queue);
+void *nghttp2_queue_front(nghttp2_queue *queue);
+void *nghttp2_queue_back(nghttp2_queue *queue);
+int nghttp2_queue_empty(nghttp2_queue *queue);
+
+#endif /* NGHTTP2_QUEUE_H */
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
new file mode 100644 (file)
index 0000000..0742a02
--- /dev/null
@@ -0,0 +1,6282 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_session.h"
+
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "nghttp2_helper.h"
+#include "nghttp2_net.h"
+#include "nghttp2_priority_spec.h"
+#include "nghttp2_option.h"
+
+/*
+ * Returns non-zero if the number of outgoing opened streams is larger
+ * than or equal to
+ * remote_settings.max_concurrent_streams.
+ */
+static int
+session_is_outgoing_concurrent_streams_max(nghttp2_session *session) {
+  return session->remote_settings.max_concurrent_streams <=
+         session->num_outgoing_streams;
+}
+
+/*
+ * Returns non-zero if the number of incoming opened streams is larger
+ * than or equal to
+ * local_settings.max_concurrent_streams.
+ */
+static int
+session_is_incoming_concurrent_streams_max(nghttp2_session *session) {
+  return session->local_settings.max_concurrent_streams <=
+         session->num_incoming_streams;
+}
+
+/*
+ * Returns non-zero if the number of incoming opened streams is larger
+ * than or equal to
+ * session->pending_local_max_concurrent_stream.
+ */
+static int
+session_is_incoming_concurrent_streams_pending_max(nghttp2_session *session) {
+  return session->pending_local_max_concurrent_stream <=
+         session->num_incoming_streams;
+}
+
+/*
+ * Returns non-zero if |lib_error| is non-fatal error.
+ */
+static int is_non_fatal(int lib_error) {
+  return lib_error < 0 && lib_error > NGHTTP2_ERR_FATAL;
+}
+
+int nghttp2_is_fatal(int lib_error) { return lib_error < NGHTTP2_ERR_FATAL; }
+
+/* Returns nonzero if the |stream| is in reserved(remote) state */
+static int state_reserved_remote(nghttp2_session *session,
+                                 nghttp2_stream *stream) {
+  return stream->state == NGHTTP2_STREAM_RESERVED &&
+         !nghttp2_session_is_my_stream_id(session, stream->stream_id);
+}
+
+/* Returns nonzero if the |stream| is in reserved(local) state */
+static int state_reserved_local(nghttp2_session *session,
+                                nghttp2_stream *stream) {
+  return stream->state == NGHTTP2_STREAM_RESERVED &&
+         nghttp2_session_is_my_stream_id(session, stream->stream_id);
+}
+
+/*
+ * Checks whether received stream_id is valid.  This function returns
+ * 1 if it succeeds, or 0.
+ */
+static int session_is_new_peer_stream_id(nghttp2_session *session,
+                                         int32_t stream_id) {
+  return stream_id != 0 &&
+         !nghttp2_session_is_my_stream_id(session, stream_id) &&
+         session->last_recv_stream_id < stream_id;
+}
+
+static int session_detect_idle_stream(nghttp2_session *session,
+                                      int32_t stream_id) {
+  /* Assume that stream object with stream_id does not exist */
+  if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+    if (session->next_stream_id <= (uint32_t)stream_id) {
+      return 1;
+    }
+    return 0;
+  }
+  if (session_is_new_peer_stream_id(session, stream_id)) {
+    return 1;
+  }
+  return 0;
+}
+
+static int session_terminate_session(nghttp2_session *session,
+                                     int32_t last_stream_id,
+                                     uint32_t error_code, const char *reason) {
+  int rv;
+  const uint8_t *debug_data;
+  size_t debug_datalen;
+
+  if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) {
+    return 0;
+  }
+
+  if (reason == NULL) {
+    debug_data = NULL;
+    debug_datalen = 0;
+  } else {
+    debug_data = (const uint8_t *)reason;
+    debug_datalen = strlen(reason);
+  }
+
+  rv = nghttp2_session_add_goaway(session, last_stream_id, error_code,
+                                  debug_data, debug_datalen,
+                                  NGHTTP2_GOAWAY_AUX_TERM_ON_SEND);
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  session->goaway_flags |= NGHTTP2_GOAWAY_TERM_ON_SEND;
+
+  return 0;
+}
+
+int nghttp2_session_terminate_session(nghttp2_session *session,
+                                      uint32_t error_code) {
+  return session_terminate_session(session, session->last_proc_stream_id,
+                                   error_code, NULL);
+}
+
+int nghttp2_session_terminate_session2(nghttp2_session *session,
+                                       int32_t last_stream_id,
+                                       uint32_t error_code) {
+  return session_terminate_session(session, last_stream_id, error_code, NULL);
+}
+
+int nghttp2_session_terminate_session_with_reason(nghttp2_session *session,
+                                                  uint32_t error_code,
+                                                  const char *reason) {
+  return session_terminate_session(session, session->last_proc_stream_id,
+                                   error_code, reason);
+}
+
+int nghttp2_session_is_my_stream_id(nghttp2_session *session,
+                                    int32_t stream_id) {
+  int rem;
+  if (stream_id == 0) {
+    return 0;
+  }
+  rem = stream_id & 0x1;
+  if (session->server) {
+    return rem == 0;
+  }
+  return rem == 1;
+}
+
+nghttp2_stream *nghttp2_session_get_stream(nghttp2_session *session,
+                                           int32_t stream_id) {
+  nghttp2_stream *stream;
+
+  stream = (nghttp2_stream *)nghttp2_map_find(&session->streams, stream_id);
+
+  if (stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) ||
+      stream->state == NGHTTP2_STREAM_IDLE) {
+    return NULL;
+  }
+
+  return stream;
+}
+
+nghttp2_stream *nghttp2_session_get_stream_raw(nghttp2_session *session,
+                                               int32_t stream_id) {
+  return (nghttp2_stream *)nghttp2_map_find(&session->streams, stream_id);
+}
+
+static int outbound_item_compar(const void *lhsx, const void *rhsx) {
+  const nghttp2_outbound_item *lhs, *rhs;
+
+  lhs = (const nghttp2_outbound_item *)lhsx;
+  rhs = (const nghttp2_outbound_item *)rhsx;
+
+  if (lhs->cycle == rhs->cycle) {
+    if (lhs->weight == rhs->weight) {
+      return (lhs->seq < rhs->seq) ? -1 : ((lhs->seq > rhs->seq) ? 1 : 0);
+    }
+
+    /* Larger weight has higher precedence */
+    return rhs->weight - lhs->weight;
+  }
+
+  return (lhs->cycle < rhs->cycle) ? -1 : 1;
+}
+
+static void session_inbound_frame_reset(nghttp2_session *session) {
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_mem *mem = &session->mem;
+  /* A bit risky code, since if this function is called from
+     nghttp2_session_new(), we rely on the fact that
+     iframe->frame.hd.type is 0, so that no free is performed. */
+  switch (iframe->frame.hd.type) {
+  case NGHTTP2_HEADERS:
+    nghttp2_frame_headers_free(&iframe->frame.headers, mem);
+    break;
+  case NGHTTP2_PRIORITY:
+    nghttp2_frame_priority_free(&iframe->frame.priority);
+    break;
+  case NGHTTP2_RST_STREAM:
+    nghttp2_frame_rst_stream_free(&iframe->frame.rst_stream);
+    break;
+  case NGHTTP2_SETTINGS:
+    nghttp2_frame_settings_free(&iframe->frame.settings, mem);
+    break;
+  case NGHTTP2_PUSH_PROMISE:
+    nghttp2_frame_push_promise_free(&iframe->frame.push_promise, mem);
+    break;
+  case NGHTTP2_PING:
+    nghttp2_frame_ping_free(&iframe->frame.ping);
+    break;
+  case NGHTTP2_GOAWAY:
+    nghttp2_frame_goaway_free(&iframe->frame.goaway, mem);
+    break;
+  case NGHTTP2_WINDOW_UPDATE:
+    nghttp2_frame_window_update_free(&iframe->frame.window_update);
+    break;
+  }
+
+  memset(&iframe->frame, 0, sizeof(nghttp2_frame));
+  memset(&iframe->ext_frame_payload, 0, sizeof(nghttp2_ext_frame_payload));
+
+  iframe->state = NGHTTP2_IB_READ_HEAD;
+
+  nghttp2_buf_wrap_init(&iframe->sbuf, iframe->raw_sbuf,
+                        sizeof(iframe->raw_sbuf));
+  iframe->sbuf.mark += NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_buf_free(&iframe->lbuf, mem);
+  nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);
+
+  iframe->niv = 0;
+  iframe->payloadleft = 0;
+  iframe->padlen = 0;
+  iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].settings_id =
+      NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].value = UINT32_MAX;
+}
+
+static void init_settings(nghttp2_settings_storage *settings) {
+  settings->header_table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
+  settings->enable_push = 1;
+  settings->max_concurrent_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+  settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
+  settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN;
+  settings->max_header_list_size = UINT32_MAX;
+}
+
+static void active_outbound_item_reset(nghttp2_active_outbound_item *aob,
+                                       nghttp2_mem *mem) {
+  DEBUGF(fprintf(stderr, "send: reset nghttp2_active_outbound_item\n"));
+  DEBUGF(fprintf(stderr, "send: aob->item = %p\n", aob->item));
+  nghttp2_outbound_item_free(aob->item, mem);
+  nghttp2_mem_free(mem, aob->item);
+  aob->item = NULL;
+  nghttp2_bufs_reset(&aob->framebufs);
+  aob->state = NGHTTP2_OB_POP_ITEM;
+}
+
+/* This global variable exists for tests where we want to disable this
+   check. */
+int nghttp2_enable_strict_first_settings_check = 1;
+
+static int session_new(nghttp2_session **session_ptr,
+                       const nghttp2_session_callbacks *callbacks,
+                       void *user_data, int server,
+                       const nghttp2_option *option, nghttp2_mem *mem) {
+  int rv;
+
+  if (mem == NULL) {
+    mem = nghttp2_mem_default();
+  }
+
+  *session_ptr = nghttp2_mem_calloc(mem, 1, sizeof(nghttp2_session));
+  if (*session_ptr == NULL) {
+    rv = NGHTTP2_ERR_NOMEM;
+    goto fail_session;
+  }
+
+  (*session_ptr)->mem = *mem;
+  mem = &(*session_ptr)->mem;
+
+  /* next_stream_id is initialized in either
+     nghttp2_session_client_new2 or nghttp2_session_server_new2 */
+
+  rv = nghttp2_pq_init(&(*session_ptr)->ob_pq, outbound_item_compar, mem);
+  if (rv != 0) {
+    goto fail_ob_pq;
+  }
+  rv = nghttp2_pq_init(&(*session_ptr)->ob_ss_pq, outbound_item_compar, mem);
+  if (rv != 0) {
+    goto fail_ob_ss_pq;
+  }
+  rv = nghttp2_pq_init(&(*session_ptr)->ob_da_pq, outbound_item_compar, mem);
+  if (rv != 0) {
+    goto fail_ob_da_pq;
+  }
+
+  rv = nghttp2_hd_deflate_init(&(*session_ptr)->hd_deflater, mem);
+  if (rv != 0) {
+    goto fail_hd_deflater;
+  }
+  rv = nghttp2_hd_inflate_init(&(*session_ptr)->hd_inflater, mem);
+  if (rv != 0) {
+    goto fail_hd_inflater;
+  }
+  rv = nghttp2_map_init(&(*session_ptr)->streams, mem);
+  if (rv != 0) {
+    goto fail_map;
+  }
+
+  nghttp2_stream_roots_init(&(*session_ptr)->roots);
+
+  (*session_ptr)->next_seq = 0;
+  (*session_ptr)->last_cycle = 1;
+
+  (*session_ptr)->remote_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
+  (*session_ptr)->recv_window_size = 0;
+  (*session_ptr)->consumed_size = 0;
+  (*session_ptr)->recv_reduction = 0;
+  (*session_ptr)->local_window_size = NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
+
+  (*session_ptr)->goaway_flags = NGHTTP2_GOAWAY_NONE;
+  (*session_ptr)->local_last_stream_id = (1u << 31) - 1;
+  (*session_ptr)->remote_last_stream_id = (1u << 31) - 1;
+
+  (*session_ptr)->inflight_niv = -1;
+
+  (*session_ptr)->pending_local_max_concurrent_stream =
+      NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+
+  if (server) {
+    (*session_ptr)->server = 1;
+  }
+
+  /* 1 for Pad Field. */
+  rv = nghttp2_bufs_init3(&(*session_ptr)->aob.framebufs,
+                          NGHTTP2_FRAMEBUF_CHUNKLEN, NGHTTP2_FRAMEBUF_MAX_NUM,
+                          1, NGHTTP2_FRAME_HDLEN + 1, mem);
+  if (rv != 0) {
+    goto fail_aob_framebuf;
+  }
+
+  active_outbound_item_reset(&(*session_ptr)->aob, mem);
+
+  init_settings(&(*session_ptr)->remote_settings);
+  init_settings(&(*session_ptr)->local_settings);
+
+  if (option) {
+    if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
+        option->no_auto_window_update) {
+
+      (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE;
+    }
+
+    if (option->opt_set_mask & NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS) {
+
+      (*session_ptr)->remote_settings.max_concurrent_streams =
+          option->peer_max_concurrent_streams;
+    }
+
+    if (option->opt_set_mask & NGHTTP2_OPT_RECV_CLIENT_PREFACE) {
+
+      (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE;
+    }
+  }
+
+  (*session_ptr)->callbacks = *callbacks;
+  (*session_ptr)->user_data = user_data;
+
+  session_inbound_frame_reset(*session_ptr);
+
+  if (server &&
+      ((*session_ptr)->opt_flags & NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE)) {
+
+    nghttp2_inbound_frame *iframe = &(*session_ptr)->iframe;
+
+    iframe->state = NGHTTP2_IB_READ_CLIENT_PREFACE;
+    iframe->payloadleft = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
+  } else if (nghttp2_enable_strict_first_settings_check) {
+    nghttp2_inbound_frame *iframe = &(*session_ptr)->iframe;
+
+    iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS;
+  }
+
+  return 0;
+
+fail_aob_framebuf:
+  nghttp2_map_free(&(*session_ptr)->streams);
+fail_map:
+  nghttp2_hd_inflate_free(&(*session_ptr)->hd_inflater);
+fail_hd_inflater:
+  nghttp2_hd_deflate_free(&(*session_ptr)->hd_deflater);
+fail_hd_deflater:
+  nghttp2_pq_free(&(*session_ptr)->ob_da_pq);
+fail_ob_da_pq:
+  nghttp2_pq_free(&(*session_ptr)->ob_ss_pq);
+fail_ob_ss_pq:
+  nghttp2_pq_free(&(*session_ptr)->ob_pq);
+fail_ob_pq:
+  nghttp2_mem_free(mem, *session_ptr);
+fail_session:
+  return rv;
+}
+
+int nghttp2_session_client_new(nghttp2_session **session_ptr,
+                               const nghttp2_session_callbacks *callbacks,
+                               void *user_data) {
+  return nghttp2_session_client_new3(session_ptr, callbacks, user_data, NULL,
+                                     NULL);
+}
+
+int nghttp2_session_client_new2(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option) {
+  return nghttp2_session_client_new3(session_ptr, callbacks, user_data, option,
+                                     NULL);
+}
+
+int nghttp2_session_client_new3(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option,
+                                nghttp2_mem *mem) {
+  int rv;
+  nghttp2_session *session;
+
+  rv = session_new(&session, callbacks, user_data, 0, option, mem);
+
+  if (rv != 0) {
+    return rv;
+  }
+  /* IDs for use in client */
+  session->next_stream_id = 1;
+
+  *session_ptr = session;
+
+  return 0;
+}
+
+int nghttp2_session_server_new(nghttp2_session **session_ptr,
+                               const nghttp2_session_callbacks *callbacks,
+                               void *user_data) {
+  return nghttp2_session_server_new3(session_ptr, callbacks, user_data, NULL,
+                                     NULL);
+}
+
+int nghttp2_session_server_new2(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option) {
+  return nghttp2_session_server_new3(session_ptr, callbacks, user_data, option,
+                                     NULL);
+}
+
+int nghttp2_session_server_new3(nghttp2_session **session_ptr,
+                                const nghttp2_session_callbacks *callbacks,
+                                void *user_data, const nghttp2_option *option,
+                                nghttp2_mem *mem) {
+  int rv;
+  nghttp2_session *session;
+
+  rv = session_new(&session, callbacks, user_data, 1, option, mem);
+
+  if (rv != 0) {
+    return rv;
+  }
+  /* IDs for use in client */
+  session->next_stream_id = 2;
+
+  *session_ptr = session;
+
+  return 0;
+}
+
+static int free_streams(nghttp2_map_entry *entry, void *ptr) {
+  nghttp2_session *session;
+  nghttp2_stream *stream;
+  nghttp2_outbound_item *item;
+  nghttp2_mem *mem;
+
+  session = (nghttp2_session *)ptr;
+  mem = &session->mem;
+  stream = (nghttp2_stream *)entry;
+  item = stream->item;
+
+  if (item && !item->queued && item != session->aob.item) {
+    nghttp2_outbound_item_free(item, mem);
+    nghttp2_mem_free(mem, item);
+  }
+
+  nghttp2_stream_free(stream);
+  nghttp2_mem_free(mem, stream);
+
+  return 0;
+}
+
+static void ob_pq_free(nghttp2_pq *pq, nghttp2_mem *mem) {
+  while (!nghttp2_pq_empty(pq)) {
+    nghttp2_outbound_item *item = (nghttp2_outbound_item *)nghttp2_pq_top(pq);
+    nghttp2_outbound_item_free(item, mem);
+    nghttp2_mem_free(mem, item);
+    nghttp2_pq_pop(pq);
+  }
+  nghttp2_pq_free(pq);
+}
+
+void nghttp2_session_del(nghttp2_session *session) {
+  nghttp2_mem *mem;
+
+  if (session == NULL) {
+    return;
+  }
+
+  mem = &session->mem;
+
+  nghttp2_mem_free(mem, session->inflight_iv);
+
+  nghttp2_stream_roots_free(&session->roots);
+
+  /* Have to free streams first, so that we can check
+     stream->item->queued */
+  nghttp2_map_each_free(&session->streams, free_streams, session);
+  nghttp2_map_free(&session->streams);
+
+  ob_pq_free(&session->ob_pq, mem);
+  ob_pq_free(&session->ob_ss_pq, mem);
+  ob_pq_free(&session->ob_da_pq, mem);
+  active_outbound_item_reset(&session->aob, mem);
+  session_inbound_frame_reset(session);
+  nghttp2_hd_deflate_free(&session->hd_deflater);
+  nghttp2_hd_inflate_free(&session->hd_inflater);
+  nghttp2_bufs_free(&session->aob.framebufs);
+  nghttp2_mem_free(mem, session);
+}
+
+int
+nghttp2_session_reprioritize_stream(nghttp2_session *session,
+                                    nghttp2_stream *stream,
+                                    const nghttp2_priority_spec *pri_spec_in) {
+  int rv;
+  nghttp2_stream *dep_stream = NULL;
+  nghttp2_stream *root_stream;
+  nghttp2_priority_spec pri_spec_default;
+  const nghttp2_priority_spec *pri_spec = pri_spec_in;
+
+  if (!nghttp2_stream_in_dep_tree(stream)) {
+    return 0;
+  }
+
+  if (pri_spec->stream_id == stream->stream_id) {
+    return nghttp2_session_terminate_session_with_reason(
+        session, NGHTTP2_PROTOCOL_ERROR, "depend on itself");
+  }
+
+  if (pri_spec->stream_id != 0) {
+    dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
+
+    if (session->server && !dep_stream &&
+        session_detect_idle_stream(session, pri_spec->stream_id)) {
+
+      nghttp2_priority_spec_default_init(&pri_spec_default);
+
+      dep_stream = nghttp2_session_open_stream(
+          session, pri_spec->stream_id, NGHTTP2_FLAG_NONE, &pri_spec_default,
+          NGHTTP2_STREAM_IDLE, NULL);
+
+      if (dep_stream == NULL) {
+        return NGHTTP2_ERR_NOMEM;
+      }
+    } else if (!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) {
+      nghttp2_priority_spec_default_init(&pri_spec_default);
+      pri_spec = &pri_spec_default;
+    }
+  }
+
+  if (pri_spec->stream_id == 0) {
+    nghttp2_stream_dep_remove_subtree(stream);
+
+    /* We have to update weight after removing stream from tree */
+    stream->weight = pri_spec->weight;
+
+    if (pri_spec->exclusive &&
+        session->roots.num_streams <= NGHTTP2_MAX_DEP_TREE_LENGTH) {
+
+      rv = nghttp2_stream_dep_all_your_stream_are_belong_to_us(stream, session);
+    } else {
+      rv = nghttp2_stream_dep_make_root(stream, session);
+    }
+
+    return rv;
+  }
+
+  assert(dep_stream);
+
+  if (nghttp2_stream_dep_subtree_find(stream, dep_stream)) {
+    DEBUGF(fprintf(stderr, "stream: cycle detected, dep_stream(%p)=%d "
+                           "stream(%p)=%d\n",
+                   dep_stream, dep_stream->stream_id, stream,
+                   stream->stream_id));
+
+    nghttp2_stream_dep_remove_subtree(dep_stream);
+    nghttp2_stream_dep_make_root(dep_stream, session);
+  }
+
+  nghttp2_stream_dep_remove_subtree(stream);
+
+  /* We have to update weight after removing stream from tree */
+  stream->weight = pri_spec->weight;
+
+  root_stream = nghttp2_stream_get_dep_root(dep_stream);
+
+  if (root_stream->num_substreams + stream->num_substreams >
+      NGHTTP2_MAX_DEP_TREE_LENGTH) {
+    stream->weight = NGHTTP2_DEFAULT_WEIGHT;
+
+    rv = nghttp2_stream_dep_make_root(stream, session);
+  } else {
+    if (pri_spec->exclusive) {
+      rv = nghttp2_stream_dep_insert_subtree(dep_stream, stream, session);
+    } else {
+      rv = nghttp2_stream_dep_add_subtree(dep_stream, stream, session);
+    }
+  }
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+void nghttp2_session_outbound_item_init(nghttp2_session *session,
+                                        nghttp2_outbound_item *item) {
+  item->seq = session->next_seq++;
+  /* We use cycle for DATA only */
+  item->cycle = 0;
+  item->weight = NGHTTP2_OB_EX_WEIGHT;
+  item->queued = 0;
+
+  memset(&item->aux_data, 0, sizeof(nghttp2_aux_data));
+}
+
+int nghttp2_session_add_item(nghttp2_session *session,
+                             nghttp2_outbound_item *item) {
+  /* TODO Return error if stream is not found for the frame requiring
+     stream presence. */
+  int rv = 0;
+  nghttp2_stream *stream;
+  nghttp2_frame *frame;
+
+  frame = &item->frame;
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+  if (frame->hd.type != NGHTTP2_DATA) {
+
+    switch (frame->hd.type) {
+    case NGHTTP2_RST_STREAM:
+      if (stream) {
+        /* We rely on the stream state to decide whether number of
+           streams should be decremented or not. For purly reserved or
+           idle streams, they are not counted to those numbers and we
+           must keep this state in order not to decrement the number.
+           We don't check against NGHTTP2_STREAM_IDLE because
+           nghttp2_session_get_stream() does not return such
+           stream. */
+        if (stream->state != NGHTTP2_STREAM_RESERVED) {
+          stream->state = NGHTTP2_STREAM_CLOSING;
+        }
+      }
+
+      break;
+    case NGHTTP2_SETTINGS:
+      item->weight = NGHTTP2_OB_SETTINGS_WEIGHT;
+
+      break;
+    case NGHTTP2_PING:
+      /* Ping has highest priority. */
+      item->weight = NGHTTP2_OB_PING_WEIGHT;
+
+      break;
+    default:
+      break;
+    }
+
+    if (frame->hd.type == NGHTTP2_HEADERS) {
+      /* We push request HEADERS and push response HEADERS to
+         dedicated queue because their transmission is affected by
+         SETTINGS_MAX_CONCURRENT_STREAMS */
+      /* TODO If 2 HEADERS are submitted for reserved stream, then
+         both of them are queued into ob_ss_pq, which is not
+         desirable. */
+      if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+        rv = nghttp2_pq_push(&session->ob_ss_pq, item);
+
+        if (rv != 0) {
+          return rv;
+        }
+
+        item->queued = 1;
+      } else if (stream && (stream->state == NGHTTP2_STREAM_RESERVED ||
+                            item->aux_data.headers.attach_stream)) {
+        item->weight = stream->effective_weight;
+        item->cycle = session->last_cycle;
+
+        rv = nghttp2_stream_attach_item(stream, item, session);
+
+        if (rv != 0) {
+          return rv;
+        }
+      } else {
+        rv = nghttp2_pq_push(&session->ob_pq, item);
+
+        if (rv != 0) {
+          return rv;
+        }
+
+        item->queued = 1;
+      }
+    } else {
+      rv = nghttp2_pq_push(&session->ob_pq, item);
+
+      if (rv != 0) {
+        return rv;
+      }
+
+      item->queued = 1;
+    }
+
+    return 0;
+  }
+
+  if (!stream) {
+    return NGHTTP2_ERR_STREAM_CLOSED;
+  }
+
+  if (stream->item) {
+    return NGHTTP2_ERR_DATA_EXIST;
+  }
+
+  item->weight = stream->effective_weight;
+  item->cycle = session->last_cycle;
+
+  rv = nghttp2_stream_attach_item(stream, item, session);
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id,
+                                   uint32_t error_code) {
+  int rv;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_stream *stream;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream && stream->state == NGHTTP2_STREAM_CLOSING) {
+    return 0;
+  }
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_rst_stream_init(&frame->rst_stream, stream_id, error_code);
+  rv = nghttp2_session_add_item(session, item);
+  if (rv != 0) {
+    nghttp2_frame_rst_stream_free(&frame->rst_stream);
+    nghttp2_mem_free(mem, item);
+    return rv;
+  }
+  return 0;
+}
+
+nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
+                                            int32_t stream_id, uint8_t flags,
+                                            nghttp2_priority_spec *pri_spec_in,
+                                            nghttp2_stream_state initial_state,
+                                            void *stream_user_data) {
+  int rv;
+  nghttp2_stream *stream;
+  nghttp2_stream *dep_stream = NULL;
+  nghttp2_stream *root_stream;
+  int stream_alloc = 0;
+  nghttp2_priority_spec pri_spec_default;
+  nghttp2_priority_spec *pri_spec = pri_spec_in;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  stream = nghttp2_session_get_stream_raw(session, stream_id);
+
+  if (stream) {
+    assert(stream->state == NGHTTP2_STREAM_IDLE);
+    assert(nghttp2_stream_in_dep_tree(stream));
+    nghttp2_session_detach_idle_stream(session, stream);
+    nghttp2_stream_dep_remove(stream);
+  } else {
+    if (session->server && initial_state != NGHTTP2_STREAM_IDLE &&
+        !nghttp2_session_is_my_stream_id(session, stream_id)) {
+
+      nghttp2_session_adjust_closed_stream(session, 1);
+    }
+
+    stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream));
+    if (stream == NULL) {
+      return NULL;
+    }
+
+    stream_alloc = 1;
+  }
+
+  if (pri_spec->stream_id != 0) {
+    dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
+
+    if (session->server && !dep_stream &&
+        session_detect_idle_stream(session, pri_spec->stream_id)) {
+      /* Depends on idle stream, which does not exist in memory.
+         Assign default priority for it. */
+      nghttp2_priority_spec_default_init(&pri_spec_default);
+
+      dep_stream = nghttp2_session_open_stream(
+          session, pri_spec->stream_id, NGHTTP2_FLAG_NONE, &pri_spec_default,
+          NGHTTP2_STREAM_IDLE, NULL);
+
+      if (dep_stream == NULL) {
+        if (stream_alloc) {
+          nghttp2_mem_free(mem, stream);
+        }
+
+        return NULL;
+      }
+    } else if (!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) {
+      /* If dep_stream is not part of dependency tree, stream will get
+         default priority. */
+      nghttp2_priority_spec_default_init(&pri_spec_default);
+      pri_spec = &pri_spec_default;
+    }
+  }
+
+  nghttp2_stream_init(
+      stream, stream_id, flags, initial_state, pri_spec->weight,
+      &session->roots, session->remote_settings.initial_window_size,
+      session->local_settings.initial_window_size, stream_user_data);
+
+  if (stream_alloc) {
+    rv = nghttp2_map_insert(&session->streams, &stream->map_entry);
+    if (rv != 0) {
+      nghttp2_mem_free(mem, stream);
+      return NULL;
+    }
+  }
+
+  switch (initial_state) {
+  case NGHTTP2_STREAM_RESERVED:
+    if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+      /* half closed (remote) */
+      nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+    } else {
+      /* half closed (local) */
+      nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+    }
+    /* Reserved stream does not count in the concurrent streams
+       limit. That is one of the DOS vector. */
+    break;
+  case NGHTTP2_STREAM_IDLE:
+    /* Idle stream does not count toward the concurrent streams limit.
+       This is used as anchor node in dependency tree. */
+    assert(session->server);
+    nghttp2_session_keep_idle_stream(session, stream);
+    break;
+  default:
+    if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+      ++session->num_outgoing_streams;
+    } else {
+      ++session->num_incoming_streams;
+    }
+  }
+
+  /* We don't have to track dependency of received reserved stream */
+  if (stream->shut_flags & NGHTTP2_SHUT_WR) {
+    return stream;
+  }
+
+  if (pri_spec->stream_id == 0) {
+
+    ++session->roots.num_streams;
+
+    if (pri_spec->exclusive &&
+        session->roots.num_streams <= NGHTTP2_MAX_DEP_TREE_LENGTH) {
+      rv = nghttp2_stream_dep_all_your_stream_are_belong_to_us(stream, session);
+
+      /* Since no dpri is changed in dependency tree, the above
+         function call never fail. */
+      assert(rv == 0);
+    } else {
+      nghttp2_stream_roots_add(&session->roots, stream);
+    }
+
+    return stream;
+  }
+
+  /* TODO Client does not have to track dependencies of streams except
+     for those which have upload data.  Currently, we just track
+     everything. */
+
+  assert(dep_stream);
+
+  root_stream = nghttp2_stream_get_dep_root(dep_stream);
+
+  if (root_stream->num_substreams < NGHTTP2_MAX_DEP_TREE_LENGTH) {
+    if (pri_spec->exclusive) {
+      nghttp2_stream_dep_insert(dep_stream, stream);
+    } else {
+      nghttp2_stream_dep_add(dep_stream, stream);
+    }
+  } else {
+    stream->weight = NGHTTP2_DEFAULT_WEIGHT;
+
+    nghttp2_stream_roots_add(&session->roots, stream);
+  }
+
+  return stream;
+}
+
+int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
+                                 uint32_t error_code) {
+  int rv;
+  nghttp2_stream *stream;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  stream = nghttp2_session_get_stream(session, stream_id);
+
+  if (!stream) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  DEBUGF(fprintf(stderr, "stream: stream(%p)=%d close\n", stream,
+                 stream->stream_id));
+
+  if (stream->item) {
+    nghttp2_outbound_item *item;
+
+    item = stream->item;
+
+    rv = nghttp2_stream_detach_item(stream, session);
+
+    if (rv != 0) {
+      return rv;
+    }
+
+    /* If item is queued, it will be deleted when it is popped
+       (nghttp2_session_prep_frame() will fail).  If session->aob.item
+       points to this item, let active_outbound_item_reset()
+       free the item. */
+    if (!item->queued && item != session->aob.item) {
+      nghttp2_outbound_item_free(item, mem);
+      nghttp2_mem_free(mem, item);
+    }
+  }
+
+  /* We call on_stream_close_callback even if stream->state is
+     NGHTTP2_STREAM_INITIAL. This will happen while sending request
+     HEADERS, a local endpoint receives RST_STREAM for that stream. It
+     may be PROTOCOL_ERROR, but without notifying stream closure will
+     hang the stream in a local endpoint.
+  */
+
+  if (session->callbacks.on_stream_close_callback) {
+    if (session->callbacks.on_stream_close_callback(
+            session, stream_id, error_code, session->user_data) != 0) {
+
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+
+  switch (stream->state) {
+  case NGHTTP2_STREAM_RESERVED:
+    break;
+  default:
+    if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+      --session->num_outgoing_streams;
+    } else {
+      --session->num_incoming_streams;
+    }
+  }
+
+  /* Closes both directions just in case they are not closed yet */
+  stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED;
+
+  if (session->server && nghttp2_stream_in_dep_tree(stream)) {
+    /* On server side, retain stream at most MAX_CONCURRENT_STREAMS
+       combined with the current active incoming streams to make
+       dependency tree work better. */
+    nghttp2_session_keep_closed_stream(session, stream);
+  } else {
+    nghttp2_session_destroy_stream(session, stream);
+  }
+
+  return 0;
+}
+
+void nghttp2_session_destroy_stream(nghttp2_session *session,
+                                    nghttp2_stream *stream) {
+  nghttp2_mem *mem;
+
+  DEBUGF(fprintf(stderr, "stream: destroy closed stream(%p)=%d\n", stream,
+                 stream->stream_id));
+
+  mem = &session->mem;
+
+  nghttp2_stream_dep_remove(stream);
+
+  nghttp2_map_remove(&session->streams, stream->stream_id);
+  nghttp2_stream_free(stream);
+  nghttp2_mem_free(mem, stream);
+}
+
+void nghttp2_session_keep_closed_stream(nghttp2_session *session,
+                                        nghttp2_stream *stream) {
+  DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n",
+                 stream, stream->stream_id, stream->state));
+
+  if (session->closed_stream_tail) {
+    session->closed_stream_tail->closed_next = stream;
+    stream->closed_prev = session->closed_stream_tail;
+  } else {
+    session->closed_stream_head = stream;
+  }
+  session->closed_stream_tail = stream;
+
+  ++session->num_closed_streams;
+
+  nghttp2_session_adjust_closed_stream(session, 0);
+}
+
+void nghttp2_session_keep_idle_stream(nghttp2_session *session,
+                                      nghttp2_stream *stream) {
+  DEBUGF(fprintf(stderr, "stream: keep idle stream(%p)=%d, state=%d\n", stream,
+                 stream->stream_id, stream->state));
+
+  if (session->idle_stream_tail) {
+    session->idle_stream_tail->closed_next = stream;
+    stream->closed_prev = session->idle_stream_tail;
+  } else {
+    session->idle_stream_head = stream;
+  }
+  session->idle_stream_tail = stream;
+
+  ++session->num_idle_streams;
+
+  nghttp2_session_adjust_idle_stream(session);
+}
+
+void nghttp2_session_detach_idle_stream(nghttp2_session *session,
+                                        nghttp2_stream *stream) {
+  nghttp2_stream *prev_stream, *next_stream;
+
+  DEBUGF(fprintf(stderr, "stream: detach idle stream(%p)=%d, state=%d\n",
+                 stream, stream->stream_id, stream->state));
+
+  prev_stream = stream->closed_prev;
+  next_stream = stream->closed_next;
+
+  if (prev_stream) {
+    prev_stream->closed_next = next_stream;
+  } else {
+    session->idle_stream_head = next_stream;
+  }
+
+  if (next_stream) {
+    next_stream->closed_prev = prev_stream;
+  } else {
+    session->idle_stream_tail = prev_stream;
+  }
+
+  stream->closed_prev = NULL;
+  stream->closed_next = NULL;
+
+  --session->num_idle_streams;
+}
+
+void nghttp2_session_adjust_closed_stream(nghttp2_session *session,
+                                          ssize_t offset) {
+  size_t num_stream_max;
+
+  num_stream_max = nghttp2_min(session->local_settings.max_concurrent_streams,
+                               session->pending_local_max_concurrent_stream);
+
+  DEBUGF(fprintf(stderr, "stream: adjusting kept closed streams "
+                         "num_closed_streams=%zu, num_incoming_streams=%zu, "
+                         "max_concurrent_streams=%zu\n",
+                 session->num_closed_streams, session->num_incoming_streams,
+                 num_stream_max));
+
+  while (session->num_closed_streams > 0 &&
+         session->num_closed_streams + session->num_incoming_streams + offset >
+             num_stream_max) {
+    nghttp2_stream *head_stream;
+
+    head_stream = session->closed_stream_head;
+
+    assert(head_stream);
+
+    session->closed_stream_head = head_stream->closed_next;
+
+    if (session->closed_stream_head) {
+      session->closed_stream_head->closed_prev = NULL;
+    } else {
+      session->closed_stream_tail = NULL;
+    }
+
+    nghttp2_session_destroy_stream(session, head_stream);
+    /* head_stream is now freed */
+    --session->num_closed_streams;
+  }
+}
+
+void nghttp2_session_adjust_idle_stream(nghttp2_session *session) {
+  size_t max;
+
+  /* Make minimum number of idle streams 2 so that allocating 2
+     streams at once is easy.  This happens when PRIORITY frame to
+     idle stream, which depends on idle stream which does not
+     exist. */
+  max =
+      nghttp2_max(2, nghttp2_min(session->local_settings.max_concurrent_streams,
+                                 session->pending_local_max_concurrent_stream));
+
+  DEBUGF(fprintf(stderr, "stream: adjusting kept idle streams "
+                         "num_idle_streams=%zu, max=%zu\n",
+                 session->num_idle_streams, max));
+
+  while (session->num_idle_streams > max) {
+    nghttp2_stream *head;
+
+    head = session->idle_stream_head;
+    assert(head);
+
+    session->idle_stream_head = head->closed_next;
+
+    if (session->idle_stream_head) {
+      session->idle_stream_head->closed_prev = NULL;
+    } else {
+      session->idle_stream_tail = NULL;
+    }
+
+    nghttp2_session_destroy_stream(session, head);
+    /* head is now destroyed */
+    --session->num_idle_streams;
+  }
+}
+
+/*
+ * Closes stream with stream ID |stream_id| if both transmission and
+ * reception of the stream were disallowed. The |error_code| indicates
+ * the reason of the closure.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *   The stream is not found.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *   The callback function failed.
+ */
+int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
+                                              nghttp2_stream *stream) {
+  if ((stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR) {
+    return nghttp2_session_close_stream(session, stream->stream_id,
+                                        NGHTTP2_NO_ERROR);
+  }
+  return 0;
+}
+
+/*
+ * This function returns nonzero if session is closing.
+ */
+static int session_is_closing(nghttp2_session *session) {
+  return (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) != 0;
+}
+
+/*
+ * Check that we can send a frame to the |stream|. This function
+ * returns 0 if we can send a frame to the |frame|, or one of the
+ * following negative error codes:
+ *
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *   The stream is already closed.
+ * NGHTTP2_ERR_STREAM_SHUT_WR
+ *   The stream is half-closed for transmission.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int session_predicate_for_stream_send(nghttp2_session *session,
+                                             nghttp2_stream *stream) {
+  if (stream == NULL) {
+    return NGHTTP2_ERR_STREAM_CLOSED;
+  }
+  if (session_is_closing(session)) {
+    return NGHTTP2_ERR_SESSION_CLOSING;
+  }
+  if (stream->shut_flags & NGHTTP2_SHUT_WR) {
+    return NGHTTP2_ERR_STREAM_SHUT_WR;
+  }
+  return 0;
+}
+
+/*
+ * This function checks HEADERS frame |frame|, which opens stream, can
+ * be sent at this time.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
+ *     New stream cannot be created because of GOAWAY: session is
+ *     going down or received last_stream_id is strictly less than
+ *     frame->hd.stream_id.
+ */
+static int session_predicate_request_headers_send(nghttp2_session *session) {
+  /* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND) or
+     GOAWAY was received from peer, new request is not allowed. */
+  if (session->goaway_flags &
+      (NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) {
+    return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
+  }
+  return 0;
+}
+
+/*
+ * This function checks HEADERS, which is the first frame from the
+ * server, with the |stream| can be sent at this time.  The |stream|
+ * can be NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *     The stream is already closed or does not exist.
+ * NGHTTP2_ERR_STREAM_SHUT_WR
+ *     The transmission is not allowed for this stream (e.g., a frame
+ *     with END_STREAM flag set has already sent)
+ * NGHTTP2_ERR_INVALID_STREAM_ID
+ *     The stream ID is invalid.
+ * NGHTTP2_ERR_STREAM_CLOSING
+ *     RST_STREAM was queued for this stream.
+ * NGHTTP2_ERR_INVALID_STREAM_STATE
+ *     The state of the stream is not valid.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int session_predicate_response_headers_send(nghttp2_session *session,
+                                                   nghttp2_stream *stream) {
+  int rv;
+  rv = session_predicate_for_stream_send(session, stream);
+  if (rv != 0) {
+    return rv;
+  }
+  assert(stream);
+  if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) {
+    return NGHTTP2_ERR_INVALID_STREAM_ID;
+  }
+  if (stream->state == NGHTTP2_STREAM_OPENING) {
+    return 0;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_STREAM_CLOSING;
+  }
+  return NGHTTP2_ERR_INVALID_STREAM_STATE;
+}
+
+/*
+ * This function checks HEADERS for reserved stream can be sent. The
+ * |stream| must be reserved state and the |session| is server side.
+ * The |stream| can be NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * error codes:
+ *
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *   The stream is already closed.
+ * NGHTTP2_ERR_STREAM_SHUT_WR
+ *   The stream is half-closed for transmission.
+ * NGHTTP2_ERR_PROTO
+ *   The stream is not reserved state
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *   RST_STREAM was queued for this stream.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int
+session_predicate_push_response_headers_send(nghttp2_session *session,
+                                             nghttp2_stream *stream) {
+  int rv;
+  /* TODO Should disallow HEADERS if GOAWAY has already been issued? */
+  rv = session_predicate_for_stream_send(session, stream);
+  if (rv != 0) {
+    return rv;
+  }
+  assert(stream);
+  if (stream->state != NGHTTP2_STREAM_RESERVED) {
+    return NGHTTP2_ERR_PROTO;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_STREAM_CLOSING;
+  }
+  return 0;
+}
+
+/*
+ * This function checks HEADERS, which is neither stream-opening nor
+ * first response header, with the |stream| can be sent at this time.
+ * The |stream| can be NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *     The stream is already closed or does not exist.
+ * NGHTTP2_ERR_STREAM_SHUT_WR
+ *     The transmission is not allowed for this stream (e.g., a frame
+ *     with END_STREAM flag set has already sent)
+ * NGHTTP2_ERR_STREAM_CLOSING
+ *     RST_STREAM was queued for this stream.
+ * NGHTTP2_ERR_INVALID_STREAM_STATE
+ *     The state of the stream is not valid.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int session_predicate_headers_send(nghttp2_session *session,
+                                          nghttp2_stream *stream) {
+  int rv;
+  rv = session_predicate_for_stream_send(session, stream);
+  if (rv != 0) {
+    return rv;
+  }
+  assert(stream);
+  if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) {
+    if (stream->state == NGHTTP2_STREAM_CLOSING) {
+      return NGHTTP2_ERR_STREAM_CLOSING;
+    }
+    return 0;
+  }
+  if (stream->state == NGHTTP2_STREAM_OPENED) {
+    return 0;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_STREAM_CLOSING;
+  }
+  return NGHTTP2_ERR_INVALID_STREAM_STATE;
+}
+
+/*
+ * This function checks PUSH_PROMISE frame |frame| with the |stream|
+ * can be sent at this time.  The |stream| can be NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_START_STREAM_NOT_ALLOWED
+ *     New stream cannot be created because GOAWAY is already sent or
+ *     received.
+ * NGHTTP2_ERR_PROTO
+ *     The client side attempts to send PUSH_PROMISE, or the server
+ *     sends PUSH_PROMISE for the stream not initiated by the client.
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *     The stream is already closed or does not exist.
+ * NGHTTP2_ERR_STREAM_CLOSING
+ *     RST_STREAM was queued for this stream.
+ * NGHTTP2_ERR_STREAM_SHUT_WR
+ *     The transmission is not allowed for this stream (e.g., a frame
+ *     with END_STREAM flag set has already sent)
+ * NGHTTP2_ERR_PUSH_DISABLED
+ *     The remote peer disabled reception of PUSH_PROMISE.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int session_predicate_push_promise_send(nghttp2_session *session,
+                                               nghttp2_stream *stream) {
+  int rv;
+
+  if (!session->server) {
+    return NGHTTP2_ERR_PROTO;
+  }
+
+  rv = session_predicate_for_stream_send(session, stream);
+  if (rv != 0) {
+    return rv;
+  }
+
+  assert(stream);
+
+  if (session->remote_settings.enable_push == 0) {
+    return NGHTTP2_ERR_PUSH_DISABLED;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_STREAM_CLOSING;
+  }
+  if (session->goaway_flags & NGHTTP2_GOAWAY_RECV) {
+    return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
+  }
+  return 0;
+}
+
+/*
+ * This function checks WINDOW_UPDATE with the stream ID |stream_id|
+ * can be sent at this time. Note that END_STREAM flag of the previous
+ * frame does not affect the transmission of the WINDOW_UPDATE frame.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *     The stream is already closed or does not exist.
+ * NGHTTP2_ERR_STREAM_CLOSING
+ *     RST_STREAM was queued for this stream.
+ * NGHTTP2_ERR_INVALID_STREAM_STATE
+ *     The state of the stream is not valid.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int session_predicate_window_update_send(nghttp2_session *session,
+                                                int32_t stream_id) {
+  nghttp2_stream *stream;
+  if (stream_id == 0) {
+    /* Connection-level window update */
+    return 0;
+  }
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream == NULL) {
+    return NGHTTP2_ERR_STREAM_CLOSED;
+  }
+  if (session_is_closing(session)) {
+    return NGHTTP2_ERR_SESSION_CLOSING;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_STREAM_CLOSING;
+  }
+  if (state_reserved_local(session, stream)) {
+    return NGHTTP2_ERR_INVALID_STREAM_STATE;
+  }
+  return 0;
+}
+
+/* Take into account settings max frame size and both connection-level
+   flow control here */
+static ssize_t
+nghttp2_session_enforce_flow_control_limits(nghttp2_session *session,
+                                            nghttp2_stream *stream,
+                                            ssize_t requested_window_size) {
+  DEBUGF(fprintf(stderr, "send: remote windowsize connection=%d, "
+                         "remote maxframsize=%u, stream(id %d)=%d\n",
+                 session->remote_window_size,
+                 session->remote_settings.max_frame_size, stream->stream_id,
+                 stream->remote_window_size));
+
+  return nghttp2_min(nghttp2_min(nghttp2_min(requested_window_size,
+                                             stream->remote_window_size),
+                                 session->remote_window_size),
+                     (int32_t)session->remote_settings.max_frame_size);
+}
+
+/*
+ * Returns the maximum length of next data read. If the
+ * connection-level and/or stream-wise flow control are enabled, the
+ * return value takes into account those current window sizes. The remote
+ * settings for max frame size is also taken into account.
+ */
+static size_t nghttp2_session_next_data_read(nghttp2_session *session,
+                                             nghttp2_stream *stream) {
+  ssize_t window_size;
+
+  window_size = nghttp2_session_enforce_flow_control_limits(
+      session, stream, NGHTTP2_DATA_PAYLOADLEN);
+
+  DEBUGF(fprintf(stderr, "send: available window=%zd\n", window_size));
+
+  return window_size > 0 ? (size_t)window_size : 0;
+}
+
+/*
+ * This function checks DATA with the |stream| can be sent at this
+ * time.  The |stream| can be NULL.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *     The stream is already closed or does not exist.
+ * NGHTTP2_ERR_STREAM_SHUT_WR
+ *     The transmission is not allowed for this stream (e.g., a frame
+ *     with END_STREAM flag set has already sent)
+ * NGHTTP2_ERR_STREAM_CLOSING
+ *     RST_STREAM was queued for this stream.
+ * NGHTTP2_ERR_INVALID_STREAM_STATE
+ *     The state of the stream is not valid.
+ * NGHTTP2_ERR_SESSION_CLOSING
+ *   This session is closing.
+ */
+static int nghttp2_session_predicate_data_send(nghttp2_session *session,
+                                               nghttp2_stream *stream) {
+  int rv;
+  rv = session_predicate_for_stream_send(session, stream);
+  if (rv != 0) {
+    return rv;
+  }
+  assert(stream);
+  if (nghttp2_session_is_my_stream_id(session, stream->stream_id)) {
+    /* Request body data */
+    /* If stream->state is NGHTTP2_STREAM_CLOSING, RST_STREAM was
+       queued but not yet sent. In this case, we won't send DATA
+       frames. */
+    if (stream->state == NGHTTP2_STREAM_CLOSING) {
+      return NGHTTP2_ERR_STREAM_CLOSING;
+    }
+    if (stream->state == NGHTTP2_STREAM_RESERVED) {
+      return NGHTTP2_ERR_INVALID_STREAM_STATE;
+    }
+    return 0;
+  }
+  /* Response body data */
+  if (stream->state == NGHTTP2_STREAM_OPENED) {
+    return 0;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_STREAM_CLOSING;
+  }
+  return NGHTTP2_ERR_INVALID_STREAM_STATE;
+}
+
+static ssize_t session_call_select_padding(nghttp2_session *session,
+                                           const nghttp2_frame *frame,
+                                           size_t max_payloadlen) {
+  ssize_t rv;
+
+  if (frame->hd.length >= max_payloadlen) {
+    return frame->hd.length;
+  }
+
+  if (session->callbacks.select_padding_callback) {
+    size_t max_paddedlen;
+
+    max_paddedlen =
+        nghttp2_min(frame->hd.length + NGHTTP2_MAX_PADLEN, max_payloadlen);
+
+    rv = session->callbacks.select_padding_callback(
+        session, frame, max_paddedlen, session->user_data);
+    if (rv < (ssize_t)frame->hd.length || rv > (ssize_t)max_paddedlen) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    return rv;
+  }
+  return frame->hd.length;
+}
+
+/* Add padding to HEADERS or PUSH_PROMISE. We use
+   frame->headers.padlen in this function to use the fact that
+   frame->push_promise has also padlen in the same position. */
+static int session_headers_add_pad(nghttp2_session *session,
+                                   nghttp2_frame *frame) {
+  int rv;
+  ssize_t padded_payloadlen;
+  nghttp2_active_outbound_item *aob;
+  nghttp2_bufs *framebufs;
+  size_t padlen;
+  size_t max_payloadlen;
+
+  aob = &session->aob;
+  framebufs = &aob->framebufs;
+
+  max_payloadlen = nghttp2_min(NGHTTP2_MAX_PAYLOADLEN,
+                               frame->hd.length + NGHTTP2_MAX_PADLEN);
+
+  padded_payloadlen =
+      session_call_select_padding(session, frame, max_payloadlen);
+
+  if (nghttp2_is_fatal((int)padded_payloadlen)) {
+    return (int)padded_payloadlen;
+  }
+
+  padlen = padded_payloadlen - frame->hd.length;
+
+  DEBUGF(fprintf(stderr, "send: padding selected: payloadlen=%zd, padlen=%zu\n",
+                 padded_payloadlen, padlen));
+
+  rv = nghttp2_frame_add_pad(framebufs, &frame->hd, padlen);
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  frame->headers.padlen = padlen;
+
+  return 0;
+}
+
+static size_t session_estimate_headers_payload(nghttp2_session *session,
+                                               const nghttp2_nv *nva,
+                                               size_t nvlen,
+                                               size_t additional) {
+  return nghttp2_hd_deflate_bound(&session->hd_deflater, nva, nvlen) +
+         additional;
+}
+
+/*
+ * This function serializes frame for transmission.
+ *
+ * This function returns 0 if it succeeds, or one of negative error
+ * codes, including both fatal and non-fatal ones.
+ */
+static int session_prep_frame(nghttp2_session *session,
+                              nghttp2_outbound_item *item) {
+  int framerv = 0;
+  int rv;
+  nghttp2_frame *frame;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  frame = &item->frame;
+
+  if (frame->hd.type != NGHTTP2_DATA) {
+    switch (frame->hd.type) {
+    case NGHTTP2_HEADERS: {
+      nghttp2_headers_aux_data *aux_data;
+      size_t estimated_payloadlen;
+
+      aux_data = &item->aux_data.headers;
+
+      estimated_payloadlen = session_estimate_headers_payload(
+          session, frame->headers.nva, frame->headers.nvlen,
+          NGHTTP2_PRIORITY_SPECLEN);
+
+      if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) {
+        return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+      }
+
+      if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+        nghttp2_stream *stream;
+
+        /* initial HEADERS, which opens stream */
+        rv = session_predicate_request_headers_send(session);
+        if (rv != 0) {
+          return rv;
+        }
+
+        stream = nghttp2_session_open_stream(
+            session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE,
+            &frame->headers.pri_spec, NGHTTP2_STREAM_INITIAL,
+            aux_data->stream_user_data);
+
+        if (stream == NULL) {
+          return NGHTTP2_ERR_NOMEM;
+        }
+
+      } else {
+        nghttp2_stream *stream;
+
+        stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+        if (session_predicate_push_response_headers_send(session, stream) ==
+            0) {
+          frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
+
+          if (aux_data->stream_user_data) {
+            stream->stream_user_data = aux_data->stream_user_data;
+          }
+        } else if (session_predicate_response_headers_send(session, stream) ==
+                   0) {
+          frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
+        } else {
+          frame->headers.cat = NGHTTP2_HCAT_HEADERS;
+
+          rv = session_predicate_headers_send(session, stream);
+
+          if (rv != 0) {
+            if (stream && stream->item == item) {
+              int rv2;
+
+              rv2 = nghttp2_stream_detach_item(stream, session);
+
+              if (nghttp2_is_fatal(rv2)) {
+                return rv2;
+              }
+            }
+
+            return rv;
+          }
+        }
+      }
+
+      framerv = nghttp2_frame_pack_headers(
+          &session->aob.framebufs, &frame->headers, &session->hd_deflater);
+
+      if (framerv < 0) {
+        goto close_stream_return;
+      }
+
+      DEBUGF(fprintf(stderr,
+                     "send: before padding, HEADERS serialized in %zd bytes\n",
+                     nghttp2_bufs_len(&session->aob.framebufs)));
+
+      framerv = session_headers_add_pad(session, frame);
+
+      if (framerv < 0) {
+        goto close_stream_return;
+      }
+
+      DEBUGF(fprintf(stderr, "send: HEADERS finally serialized in %zd bytes\n",
+                     nghttp2_bufs_len(&session->aob.framebufs)));
+
+      break;
+
+    close_stream_return:
+
+      if (frame->headers.cat == NGHTTP2_HCAT_REQUEST &&
+          !nghttp2_is_fatal(framerv)) {
+        rv = nghttp2_session_close_stream(session, frame->hd.stream_id,
+                                          NGHTTP2_NO_ERROR);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      }
+
+      return framerv;
+    }
+    case NGHTTP2_PRIORITY: {
+      if (session_is_closing(session)) {
+        return NGHTTP2_ERR_SESSION_CLOSING;
+      }
+      /* PRIORITY frame can be sent at any time and to any stream
+         ID. */
+      framerv = nghttp2_frame_pack_priority(&session->aob.framebufs,
+                                            &frame->priority);
+      if (framerv < 0) {
+        return framerv;
+      }
+
+      /* Peer can send PRIORITY frame against idle stream to create
+         "anchor" in dependency tree.  Only client can do this in
+         nghttp2.  In nghttp2, only server retains non-active (closed
+         or idle) streams in memory, so we don't open stream here. */
+      break;
+    }
+    case NGHTTP2_RST_STREAM:
+      if (session_is_closing(session)) {
+        return NGHTTP2_ERR_SESSION_CLOSING;
+      }
+      framerv = nghttp2_frame_pack_rst_stream(&session->aob.framebufs,
+                                              &frame->rst_stream);
+      if (framerv < 0) {
+        return framerv;
+      }
+      break;
+    case NGHTTP2_SETTINGS: {
+      framerv = nghttp2_frame_pack_settings(&session->aob.framebufs,
+                                            &frame->settings);
+      if (framerv < 0) {
+        return framerv;
+      }
+      break;
+    }
+    case NGHTTP2_PUSH_PROMISE: {
+      nghttp2_stream *stream;
+      nghttp2_headers_aux_data *aux_data;
+      nghttp2_priority_spec pri_spec;
+      size_t estimated_payloadlen;
+
+      aux_data = &item->aux_data.headers;
+
+      estimated_payloadlen = session_estimate_headers_payload(
+          session, frame->push_promise.nva, frame->push_promise.nvlen, 0);
+
+      if (estimated_payloadlen > NGHTTP2_MAX_HEADERSLEN) {
+        return NGHTTP2_ERR_FRAME_SIZE_ERROR;
+      }
+
+      stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+      rv = session_predicate_push_promise_send(session, stream);
+      if (rv != 0) {
+        return rv;
+      }
+
+      assert(stream);
+
+      framerv = nghttp2_frame_pack_push_promise(
+          &session->aob.framebufs, &frame->push_promise, &session->hd_deflater);
+      if (framerv < 0) {
+        return framerv;
+      }
+      framerv = session_headers_add_pad(session, frame);
+      if (framerv < 0) {
+        return framerv;
+      }
+
+      /* TODO It is unclear reserved stream dpeneds on associated
+         stream with or without exclusive flag set */
+      nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
+                                 NGHTTP2_DEFAULT_WEIGHT, 0);
+
+      if (!nghttp2_session_open_stream(
+              session, frame->push_promise.promised_stream_id,
+              NGHTTP2_STREAM_FLAG_PUSH, &pri_spec, NGHTTP2_STREAM_RESERVED,
+              aux_data->stream_user_data)) {
+        return NGHTTP2_ERR_NOMEM;
+      }
+      break;
+    }
+    case NGHTTP2_PING:
+      if (session_is_closing(session)) {
+        return NGHTTP2_ERR_SESSION_CLOSING;
+      }
+      framerv = nghttp2_frame_pack_ping(&session->aob.framebufs, &frame->ping);
+      if (framerv < 0) {
+        return framerv;
+      }
+      break;
+    case NGHTTP2_WINDOW_UPDATE: {
+      rv = session_predicate_window_update_send(session, frame->hd.stream_id);
+      if (rv != 0) {
+        return rv;
+      }
+      framerv = nghttp2_frame_pack_window_update(&session->aob.framebufs,
+                                                 &frame->window_update);
+      if (framerv < 0) {
+        return framerv;
+      }
+      break;
+    }
+    case NGHTTP2_GOAWAY:
+      framerv =
+          nghttp2_frame_pack_goaway(&session->aob.framebufs, &frame->goaway);
+      if (framerv < 0) {
+        return framerv;
+      }
+      session->local_last_stream_id = frame->goaway.last_stream_id;
+
+      break;
+    default:
+      return NGHTTP2_ERR_INVALID_ARGUMENT;
+    }
+    return 0;
+  } else {
+    size_t next_readmax;
+    nghttp2_stream *stream;
+
+    stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+    if (stream) {
+      assert(stream->item == item);
+    }
+
+    rv = nghttp2_session_predicate_data_send(session, stream);
+    if (rv != 0) {
+      if (stream) {
+        int rv2;
+
+        rv2 = nghttp2_stream_detach_item(stream, session);
+
+        if (nghttp2_is_fatal(rv2)) {
+          return rv2;
+        }
+      }
+
+      return rv;
+    }
+    /* Assuming stream is not NULL */
+    assert(stream);
+    next_readmax = nghttp2_session_next_data_read(session, stream);
+
+    if (next_readmax == 0) {
+
+      /* This must be true since we only pop DATA frame item from
+         queue when session->remote_window_size > 0 */
+      assert(session->remote_window_size > 0);
+
+      rv = nghttp2_stream_defer_item(
+          stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL, session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      session->aob.item = NULL;
+      active_outbound_item_reset(&session->aob, mem);
+      return NGHTTP2_ERR_DEFERRED;
+    }
+
+    framerv =
+        nghttp2_session_pack_data(session, &session->aob.framebufs,
+                                  next_readmax, frame, &item->aux_data.data);
+    if (framerv == NGHTTP2_ERR_DEFERRED) {
+      rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER,
+                                     session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      session->aob.item = NULL;
+      active_outbound_item_reset(&session->aob, mem);
+      return NGHTTP2_ERR_DEFERRED;
+    }
+    if (framerv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+      rv = nghttp2_stream_detach_item(stream, session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id,
+                                          NGHTTP2_INTERNAL_ERROR);
+      if (rv != 0) {
+        return rv;
+      }
+      return framerv;
+    }
+    if (framerv < 0) {
+      rv = nghttp2_stream_detach_item(stream, session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      return framerv;
+    }
+    return 0;
+  }
+}
+
+/* Used only for tests */
+nghttp2_outbound_item *nghttp2_session_get_ob_pq_top(nghttp2_session *session) {
+  return (nghttp2_outbound_item *)nghttp2_pq_top(&session->ob_pq);
+}
+
+nghttp2_outbound_item *
+nghttp2_session_get_next_ob_item(nghttp2_session *session) {
+  nghttp2_outbound_item *item, *headers_item;
+
+  if (nghttp2_pq_empty(&session->ob_pq)) {
+    if (nghttp2_pq_empty(&session->ob_ss_pq)) {
+      if (session->remote_window_size == 0 ||
+          nghttp2_pq_empty(&session->ob_da_pq)) {
+        return NULL;
+      }
+
+      return nghttp2_pq_top(&session->ob_da_pq);
+    }
+
+    /* Return item only when concurrent connection limit is not
+       reached */
+    if (session_is_outgoing_concurrent_streams_max(session)) {
+      if (session->remote_window_size == 0 ||
+          nghttp2_pq_empty(&session->ob_da_pq)) {
+        return NULL;
+      }
+
+      return nghttp2_pq_top(&session->ob_da_pq);
+    }
+
+    return nghttp2_pq_top(&session->ob_ss_pq);
+  }
+
+  if (nghttp2_pq_empty(&session->ob_ss_pq)) {
+    return nghttp2_pq_top(&session->ob_pq);
+  }
+
+  item = nghttp2_pq_top(&session->ob_pq);
+  headers_item = nghttp2_pq_top(&session->ob_ss_pq);
+
+  if (session_is_outgoing_concurrent_streams_max(session) ||
+      item->weight > headers_item->weight ||
+      (item->weight == headers_item->weight && item->seq < headers_item->seq)) {
+    return item;
+  }
+
+  return headers_item;
+}
+
+nghttp2_outbound_item *
+nghttp2_session_pop_next_ob_item(nghttp2_session *session) {
+  nghttp2_outbound_item *item, *headers_item;
+
+  if (nghttp2_pq_empty(&session->ob_pq)) {
+    if (nghttp2_pq_empty(&session->ob_ss_pq)) {
+      if (session->remote_window_size == 0 ||
+          nghttp2_pq_empty(&session->ob_da_pq)) {
+        return NULL;
+      }
+
+      item = nghttp2_pq_top(&session->ob_da_pq);
+      nghttp2_pq_pop(&session->ob_da_pq);
+
+      item->queued = 0;
+
+      return item;
+    }
+
+    /* Pop item only when concurrent connection limit is not
+       reached */
+    if (session_is_outgoing_concurrent_streams_max(session)) {
+      if (session->remote_window_size == 0 ||
+          nghttp2_pq_empty(&session->ob_da_pq)) {
+        return NULL;
+      }
+
+      item = nghttp2_pq_top(&session->ob_da_pq);
+      nghttp2_pq_pop(&session->ob_da_pq);
+
+      item->queued = 0;
+
+      return item;
+    }
+
+    item = nghttp2_pq_top(&session->ob_ss_pq);
+    nghttp2_pq_pop(&session->ob_ss_pq);
+
+    item->queued = 0;
+
+    return item;
+  }
+
+  if (nghttp2_pq_empty(&session->ob_ss_pq)) {
+    item = nghttp2_pq_top(&session->ob_pq);
+    nghttp2_pq_pop(&session->ob_pq);
+
+    item->queued = 0;
+
+    return item;
+  }
+
+  item = nghttp2_pq_top(&session->ob_pq);
+  headers_item = nghttp2_pq_top(&session->ob_ss_pq);
+
+  if (session_is_outgoing_concurrent_streams_max(session) ||
+      item->weight > headers_item->weight ||
+      (item->weight == headers_item->weight && item->seq < headers_item->seq)) {
+    nghttp2_pq_pop(&session->ob_pq);
+
+    item->queued = 0;
+
+    return item;
+  }
+
+  nghttp2_pq_pop(&session->ob_ss_pq);
+
+  headers_item->queued = 0;
+
+  return headers_item;
+}
+
+static int session_call_before_frame_send(nghttp2_session *session,
+                                          nghttp2_frame *frame) {
+  int rv;
+  if (session->callbacks.before_frame_send_callback) {
+    rv = session->callbacks.before_frame_send_callback(session, frame,
+                                                       session->user_data);
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+static int session_call_on_frame_send(nghttp2_session *session,
+                                      nghttp2_frame *frame) {
+  int rv;
+  if (session->callbacks.on_frame_send_callback) {
+    rv = session->callbacks.on_frame_send_callback(session, frame,
+                                                   session->user_data);
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+static int find_stream_on_goaway_func(nghttp2_map_entry *entry, void *ptr) {
+  nghttp2_close_stream_on_goaway_arg *arg;
+  nghttp2_stream *stream;
+
+  arg = (nghttp2_close_stream_on_goaway_arg *)ptr;
+  stream = (nghttp2_stream *)entry;
+
+  if (nghttp2_session_is_my_stream_id(arg->session, stream->stream_id)) {
+    if (arg->incoming) {
+      return 0;
+    }
+  } else if (!arg->incoming) {
+    return 0;
+  }
+
+  if (stream->state != NGHTTP2_STREAM_IDLE &&
+      (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) == 0 &&
+      stream->stream_id > arg->last_stream_id) {
+    /* We are collecting streams to close because we cannot call
+       nghttp2_session_close_stream() inside nghttp2_map_each().
+       Reuse closed_next member.. bad choice? */
+    assert(stream->closed_next == NULL);
+    assert(stream->closed_prev == NULL);
+
+    if (arg->head) {
+      stream->closed_next = arg->head;
+      arg->head = stream;
+    } else {
+      arg->head = stream;
+    }
+  }
+
+  return 0;
+}
+
+/* Closes non-idle and non-closed streams whose stream ID >
+   last_stream_id.  If incoming is nonzero, we are going to close
+   incoming streams.  Otherwise, close outgoing streams. */
+static int session_close_stream_on_goaway(nghttp2_session *session,
+                                          int32_t last_stream_id,
+                                          int incoming) {
+  int rv;
+  nghttp2_stream *stream, *next_stream;
+  nghttp2_close_stream_on_goaway_arg arg = {session, NULL, last_stream_id,
+                                            incoming};
+
+  rv = nghttp2_map_each(&session->streams, find_stream_on_goaway_func, &arg);
+  assert(rv == 0);
+
+  stream = arg.head;
+  while (stream) {
+    next_stream = stream->closed_next;
+    stream->closed_next = NULL;
+    rv = nghttp2_session_close_stream(session, stream->stream_id,
+                                      NGHTTP2_REFUSED_STREAM);
+
+    /* stream may be deleted here */
+
+    stream = next_stream;
+
+    if (nghttp2_is_fatal(rv)) {
+      /* Clean up closed_next member just in case */
+      while (stream) {
+        next_stream = stream->closed_next;
+        stream->closed_next = NULL;
+        stream = next_stream;
+      }
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+static void session_outbound_item_cycle_weight(nghttp2_session *session,
+                                               nghttp2_outbound_item *item,
+                                               int32_t ini_weight) {
+  if (item->weight == NGHTTP2_MIN_WEIGHT || item->weight > ini_weight) {
+
+    item->weight = ini_weight;
+
+    if (item->cycle == session->last_cycle) {
+      item->cycle = ++session->last_cycle;
+    } else {
+      item->cycle = session->last_cycle;
+    }
+  } else {
+    --item->weight;
+  }
+}
+
+/*
+ * Called after a frame is sent.  This function runs
+ * on_frame_send_callback and handles stream closure upon END_STREAM
+ * or RST_STREAM.  This function does not reset session->aob.  It is a
+ * responsibility of session_after_frame_sent2.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ */
+static int session_after_frame_sent1(nghttp2_session *session) {
+  int rv;
+  nghttp2_active_outbound_item *aob = &session->aob;
+  nghttp2_outbound_item *item = aob->item;
+  nghttp2_bufs *framebufs = &aob->framebufs;
+  nghttp2_frame *frame;
+
+  frame = &item->frame;
+
+  if (frame->hd.type != NGHTTP2_DATA) {
+
+    if (frame->hd.type == NGHTTP2_HEADERS ||
+        frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+
+      if (nghttp2_bufs_next_present(framebufs)) {
+        DEBUGF(fprintf(stderr, "send: CONTINUATION exists, just return\n"));
+        return 0;
+      }
+    }
+    rv = session_call_on_frame_send(session, frame);
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+    switch (frame->hd.type) {
+    case NGHTTP2_HEADERS: {
+      nghttp2_headers_aux_data *aux_data;
+      nghttp2_stream *stream;
+
+      stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+      if (!stream) {
+        break;
+      }
+
+      if (stream->item == item) {
+        rv = nghttp2_stream_detach_item(stream, session);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      }
+
+      switch (frame->headers.cat) {
+      case NGHTTP2_HCAT_REQUEST: {
+        stream->state = NGHTTP2_STREAM_OPENING;
+        if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+          nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+        }
+        rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+        /* We assume aux_data is a pointer to nghttp2_headers_aux_data */
+        aux_data = &item->aux_data.headers;
+        if (aux_data->data_prd.read_callback) {
+          /* nghttp2_submit_data() makes a copy of aux_data->data_prd */
+          rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,
+                                   frame->hd.stream_id, &aux_data->data_prd);
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+          /* TODO nghttp2_submit_data() may fail if stream has already
+             DATA frame item.  We might have to handle it here. */
+        }
+        break;
+      }
+      case NGHTTP2_HCAT_PUSH_RESPONSE:
+        ++session->num_outgoing_streams;
+      /* Fall through */
+      case NGHTTP2_HCAT_RESPONSE:
+        stream->state = NGHTTP2_STREAM_OPENED;
+      /* Fall through */
+      case NGHTTP2_HCAT_HEADERS:
+        if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+          nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+        }
+        rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+        /* We assume aux_data is a pointer to nghttp2_headers_aux_data */
+        aux_data = &item->aux_data.headers;
+        if (aux_data->data_prd.read_callback) {
+          rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,
+                                   frame->hd.stream_id, &aux_data->data_prd);
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+          /* TODO nghttp2_submit_data() may fail if stream has already
+             DATA frame item.  We might have to handle it here. */
+        }
+        break;
+      }
+      break;
+    }
+    case NGHTTP2_PRIORITY: {
+      nghttp2_stream *stream;
+
+      if (session->server) {
+        break;
+      }
+
+      stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
+
+      if (!stream) {
+        break;
+      }
+
+      rv = nghttp2_session_reprioritize_stream(session, stream,
+                                               &frame->priority.pri_spec);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      break;
+    }
+    case NGHTTP2_RST_STREAM:
+      rv = nghttp2_session_close_stream(session, frame->hd.stream_id,
+                                        frame->rst_stream.error_code);
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+      break;
+    case NGHTTP2_GOAWAY: {
+      nghttp2_goaway_aux_data *aux_data;
+
+      aux_data = &item->aux_data.goaway;
+
+      if ((aux_data->flags & NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE) == 0) {
+
+        if (aux_data->flags & NGHTTP2_GOAWAY_AUX_TERM_ON_SEND) {
+          session->goaway_flags |= NGHTTP2_GOAWAY_TERM_SENT;
+        }
+
+        session->goaway_flags |= NGHTTP2_GOAWAY_SENT;
+
+        rv = session_close_stream_on_goaway(session,
+                                            frame->goaway.last_stream_id, 1);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      }
+
+      break;
+    }
+    default:
+      break;
+    }
+
+    return 0;
+  } else {
+    nghttp2_stream *stream;
+    nghttp2_data_aux_data *aux_data;
+
+    aux_data = &item->aux_data.data;
+
+    stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+    /* We update flow control window after a frame was completely
+       sent. This is possible because we choose payload length not to
+       exceed the window */
+    session->remote_window_size -= frame->hd.length;
+    if (stream) {
+      stream->remote_window_size -= frame->hd.length;
+    }
+
+    if (stream && aux_data->eof) {
+      rv = nghttp2_stream_detach_item(stream, session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      /* Call on_frame_send_callback after
+         nghttp2_stream_detach_item(), so that application can issue
+         nghttp2_submit_data() in the callback. */
+      if (session->callbacks.on_frame_send_callback) {
+        rv = session_call_on_frame_send(session, frame);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      }
+
+      if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+        int stream_closed;
+
+        stream_closed =
+            (stream->shut_flags & NGHTTP2_SHUT_RDWR) == NGHTTP2_SHUT_RDWR;
+
+        nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+
+        rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+        /* stream may be NULL if it was closed */
+        if (stream_closed) {
+          stream = NULL;
+        }
+      }
+      return 0;
+    }
+
+    if (session->callbacks.on_frame_send_callback) {
+      rv = session_call_on_frame_send(session, frame);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+    }
+
+    return 0;
+  }
+  /* Unreachable */
+  assert(0);
+  return 0;
+}
+
+/*
+ * Called after a frame is sent and session_after_frame_sent1.  This
+ * function is responsible to reset session->aob.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ */
+static int session_after_frame_sent2(nghttp2_session *session) {
+  int rv;
+  nghttp2_active_outbound_item *aob = &session->aob;
+  nghttp2_outbound_item *item = aob->item;
+  nghttp2_bufs *framebufs = &aob->framebufs;
+  nghttp2_frame *frame;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  frame = &item->frame;
+
+  if (frame->hd.type != NGHTTP2_DATA) {
+
+    if (frame->hd.type == NGHTTP2_HEADERS ||
+        frame->hd.type == NGHTTP2_PUSH_PROMISE) {
+
+      if (nghttp2_bufs_next_present(framebufs)) {
+        framebufs->cur = framebufs->cur->next;
+
+        DEBUGF(fprintf(stderr, "send: next CONTINUATION frame, %zu bytes\n",
+                       nghttp2_buf_len(&framebufs->cur->buf)));
+
+        return 0;
+      }
+    }
+
+    active_outbound_item_reset(&session->aob, mem);
+
+    return 0;
+  } else {
+    nghttp2_outbound_item *next_item;
+    nghttp2_stream *stream;
+    nghttp2_data_aux_data *aux_data;
+
+    aux_data = &item->aux_data.data;
+
+    /* On EOF, we have already detached data.  Please note that
+       application may issue nghttp2_submit_data() in
+       on_frame_send_callback (call from session_after_frame_sent1),
+       which attach data to stream.  We don't want to detach it. */
+    if (aux_data->eof) {
+      active_outbound_item_reset(aob, mem);
+
+      return 0;
+    }
+
+    stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+    /* If session is closed or RST_STREAM was queued, we won't send
+       further data. */
+    if (nghttp2_session_predicate_data_send(session, stream) != 0) {
+      if (stream) {
+        rv = nghttp2_stream_detach_item(stream, session);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      }
+
+      active_outbound_item_reset(aob, mem);
+
+      return 0;
+    }
+
+    /* Assuming stream is not NULL */
+    assert(stream);
+    next_item = nghttp2_session_get_next_ob_item(session);
+
+    /* Imagine we hit connection window size limit while sending DATA
+       frame.  If we decrement weight here, its stream might get
+       inferior share because the other streams' weight is not
+       decremented because of flow control. */
+    if (session->remote_window_size > 0 || stream->remote_window_size <= 0) {
+      session_outbound_item_cycle_weight(session, aob->item,
+                                         stream->effective_weight);
+    }
+
+    /* If priority of this stream is higher or equal to other stream
+       waiting at the top of the queue, we continue to send this
+       data. */
+    if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP &&
+        (next_item == NULL || outbound_item_compar(item, next_item) < 0)) {
+      size_t next_readmax;
+
+      next_readmax = nghttp2_session_next_data_read(session, stream);
+
+      if (next_readmax == 0) {
+
+        if (session->remote_window_size == 0 &&
+            stream->remote_window_size > 0) {
+
+          /* If DATA cannot be sent solely due to connection level
+             window size, just push item to queue again.  We never pop
+             DATA item while connection level window size is 0. */
+          rv = nghttp2_pq_push(&session->ob_da_pq, aob->item);
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+
+          aob->item->queued = 1;
+        } else {
+          rv = nghttp2_stream_defer_item(
+              stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL, session);
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+
+        aob->item = NULL;
+        active_outbound_item_reset(aob, mem);
+
+        return 0;
+      }
+
+      nghttp2_bufs_reset(framebufs);
+
+      rv = nghttp2_session_pack_data(session, framebufs, next_readmax, frame,
+                                     aux_data);
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+      if (rv == NGHTTP2_ERR_DEFERRED) {
+        rv = nghttp2_stream_defer_item(
+            stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER, session);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        aob->item = NULL;
+        active_outbound_item_reset(aob, mem);
+
+        return 0;
+      }
+      if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+        /* Stop DATA frame chain and issue RST_STREAM to close the
+           stream.  We don't return
+           NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE intentionally. */
+        rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id,
+                                            NGHTTP2_INTERNAL_ERROR);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        rv = nghttp2_stream_detach_item(stream, session);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        active_outbound_item_reset(aob, mem);
+
+        return 0;
+      }
+      assert(rv == 0);
+
+      return 0;
+    }
+
+    if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+      rv = nghttp2_pq_push(&session->ob_da_pq, aob->item);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      aob->item->queued = 1;
+    }
+
+    aob->item = NULL;
+    active_outbound_item_reset(&session->aob, mem);
+    return 0;
+  }
+  /* Unreachable */
+  assert(0);
+  return 0;
+}
+
+static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session,
+                                                 const uint8_t **data_ptr,
+                                                 int fast_cb) {
+  int rv;
+  nghttp2_active_outbound_item *aob;
+  nghttp2_bufs *framebufs;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  aob = &session->aob;
+  framebufs = &aob->framebufs;
+
+  *data_ptr = NULL;
+  for (;;) {
+    switch (aob->state) {
+    case NGHTTP2_OB_POP_ITEM: {
+      nghttp2_outbound_item *item;
+
+      item = nghttp2_session_pop_next_ob_item(session);
+      if (item == NULL) {
+        return 0;
+      }
+
+      if (item->frame.hd.type == NGHTTP2_DATA ||
+          item->frame.hd.type == NGHTTP2_HEADERS) {
+        nghttp2_frame *frame;
+        nghttp2_stream *stream;
+
+        frame = &item->frame;
+        stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+        if (stream && item == stream->item &&
+            stream->dpri != NGHTTP2_STREAM_DPRI_TOP) {
+          /* We have DATA with higher priority in queue within the
+             same dependency tree. */
+          break;
+        }
+      }
+
+      rv = session_prep_frame(session, item);
+      if (rv == NGHTTP2_ERR_DEFERRED) {
+        DEBUGF(fprintf(stderr, "send: frame transmission deferred\n"));
+        break;
+      }
+      if (rv < 0) {
+        DEBUGF(fprintf(stderr, "send: frame preparation failed with %s\n",
+                       nghttp2_strerror(rv)));
+        /* TODO If the error comes from compressor, the connection
+           must be closed. */
+        if (item->frame.hd.type != NGHTTP2_DATA &&
+            session->callbacks.on_frame_not_send_callback && is_non_fatal(rv)) {
+          nghttp2_frame *frame = &item->frame;
+          /* The library is responsible for the transmission of
+             WINDOW_UPDATE frame, so we don't call error callback for
+             it. */
+          if (frame->hd.type != NGHTTP2_WINDOW_UPDATE) {
+            if (session->callbacks.on_frame_not_send_callback(
+                    session, frame, rv, session->user_data) != 0) {
+
+              nghttp2_outbound_item_free(item, mem);
+              nghttp2_mem_free(mem, item);
+
+              return NGHTTP2_ERR_CALLBACK_FAILURE;
+            }
+          }
+        }
+        nghttp2_outbound_item_free(item, mem);
+        nghttp2_mem_free(mem, item);
+        active_outbound_item_reset(aob, mem);
+
+        if (rv == NGHTTP2_ERR_HEADER_COMP) {
+          /* If header compression error occurred, should terminiate
+             connection. */
+          rv = nghttp2_session_terminate_session(session,
+                                                 NGHTTP2_INTERNAL_ERROR);
+        }
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+        break;
+      }
+
+      aob->item = item;
+
+      nghttp2_bufs_rewind(framebufs);
+
+      if (item->frame.hd.type != NGHTTP2_DATA) {
+        nghttp2_frame *frame;
+
+        frame = &item->frame;
+
+        DEBUGF(fprintf(stderr, "send: next frame: payloadlen=%zu, type=%u, "
+                               "flags=0x%02x, stream_id=%d\n",
+                       frame->hd.length, frame->hd.type, frame->hd.flags,
+                       frame->hd.stream_id));
+
+        rv = session_call_before_frame_send(session, frame);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      } else {
+        DEBUGF(fprintf(stderr, "send: next frame: DATA\n"));
+      }
+
+      DEBUGF(fprintf(stderr,
+                     "send: start transmitting frame type=%u, length=%zd\n",
+                     framebufs->cur->buf.pos[3],
+                     framebufs->cur->buf.last - framebufs->cur->buf.pos));
+
+      aob->state = NGHTTP2_OB_SEND_DATA;
+
+      break;
+    }
+    case NGHTTP2_OB_SEND_DATA: {
+      size_t datalen;
+      nghttp2_buf *buf;
+
+      buf = &framebufs->cur->buf;
+
+      if (buf->pos == buf->last) {
+        DEBUGF(fprintf(stderr, "send: end transmission of a frame\n"));
+
+        /* Frame has completely sent */
+        if (fast_cb) {
+          rv = session_after_frame_sent2(session);
+        } else {
+          rv = session_after_frame_sent1(session);
+          if (rv < 0) {
+            /* FATAL */
+            assert(nghttp2_is_fatal(rv));
+            return rv;
+          }
+          rv = session_after_frame_sent2(session);
+        }
+        if (rv < 0) {
+          /* FATAL */
+          assert(nghttp2_is_fatal(rv));
+          return rv;
+        }
+        /* We have already adjusted the next state */
+        break;
+      }
+
+      *data_ptr = buf->pos;
+      datalen = nghttp2_buf_len(buf);
+
+      /* We increment the offset here. If send_callback does not send
+         everything, we will adjust it. */
+      buf->pos += datalen;
+
+      return datalen;
+    }
+    }
+  }
+}
+
+ssize_t nghttp2_session_mem_send(nghttp2_session *session,
+                                 const uint8_t **data_ptr) {
+  int rv;
+  ssize_t len;
+
+  len = nghttp2_session_mem_send_internal(session, data_ptr, 1);
+  if (len <= 0) {
+    return len;
+  }
+
+  /* We have to call session_after_frame_sent1 here to handle stream
+     closure upon transmission of frames.  Otherwise, END_STREAM may
+     be reached to client before we call nghttp2_session_mem_send
+     again and we may get exceeding number of incoming streams. */
+  rv = session_after_frame_sent1(session);
+  if (rv < 0) {
+    assert(nghttp2_is_fatal(rv));
+    return (ssize_t)rv;
+  }
+
+  return len;
+}
+
+int nghttp2_session_send(nghttp2_session *session) {
+  const uint8_t *data;
+  ssize_t datalen;
+  ssize_t sentlen;
+  nghttp2_bufs *framebufs;
+
+  framebufs = &session->aob.framebufs;
+
+  for (;;) {
+    datalen = nghttp2_session_mem_send_internal(session, &data, 0);
+    if (datalen <= 0) {
+      return (int)datalen;
+    }
+    sentlen = session->callbacks.send_callback(session, data, datalen, 0,
+                                               session->user_data);
+    if (sentlen < 0) {
+      if (sentlen == NGHTTP2_ERR_WOULDBLOCK) {
+        /* Transmission canceled. Rewind the offset */
+        framebufs->cur->buf.pos -= datalen;
+
+        return 0;
+      }
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    /* Rewind the offset to the amount of unsent bytes */
+    framebufs->cur->buf.pos -= datalen - sentlen;
+  }
+}
+
+static ssize_t session_recv(nghttp2_session *session, uint8_t *buf,
+                            size_t len) {
+  ssize_t rv;
+  rv = session->callbacks.recv_callback(session, buf, len, 0,
+                                        session->user_data);
+  if (rv > 0) {
+    if ((size_t)rv > len) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  } else if (rv < 0 && rv != NGHTTP2_ERR_WOULDBLOCK && rv != NGHTTP2_ERR_EOF) {
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+  return rv;
+}
+
+static int session_call_on_begin_frame(nghttp2_session *session,
+                                       const nghttp2_frame_hd *hd) {
+  int rv;
+
+  if (session->callbacks.on_begin_frame_callback) {
+
+    rv = session->callbacks.on_begin_frame_callback(session, hd,
+                                                    session->user_data);
+
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+
+  return 0;
+}
+
+static int session_call_on_frame_received(nghttp2_session *session,
+                                          nghttp2_frame *frame) {
+  int rv;
+  if (session->callbacks.on_frame_recv_callback) {
+    rv = session->callbacks.on_frame_recv_callback(session, frame,
+                                                   session->user_data);
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+static int session_call_on_begin_headers(nghttp2_session *session,
+                                         nghttp2_frame *frame) {
+  int rv;
+  DEBUGF(fprintf(stderr, "recv: call on_begin_headers callback stream_id=%d\n",
+                 frame->hd.stream_id));
+  if (session->callbacks.on_begin_headers_callback) {
+    rv = session->callbacks.on_begin_headers_callback(session, frame,
+                                                      session->user_data);
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+static int session_call_on_header(nghttp2_session *session,
+                                  const nghttp2_frame *frame,
+                                  const nghttp2_nv *nv) {
+  int rv;
+  if (session->callbacks.on_header_callback) {
+    rv = session->callbacks.on_header_callback(
+        session, frame, nv->name, nv->namelen, nv->value, nv->valuelen,
+        nv->flags, session->user_data);
+    if (rv == NGHTTP2_ERR_PAUSE ||
+        rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+      return rv;
+    }
+    if (rv != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+/*
+ * Handles frame size error.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory.
+ */
+static int session_handle_frame_size_error(nghttp2_session *session,
+                                           nghttp2_frame *frame _U_) {
+  /* TODO Currently no callback is called for this error, because we
+     call this callback before reading any payload */
+  return nghttp2_session_terminate_session(session, NGHTTP2_FRAME_SIZE_ERROR);
+}
+
+static int session_handle_invalid_stream(nghttp2_session *session,
+                                         nghttp2_frame *frame,
+                                         uint32_t error_code) {
+  int rv;
+  rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id, error_code);
+  if (rv != 0) {
+    return rv;
+  }
+  if (session->callbacks.on_invalid_frame_recv_callback) {
+    if (session->callbacks.on_invalid_frame_recv_callback(
+            session, frame, error_code, session->user_data) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return 0;
+}
+
+static int session_inflate_handle_invalid_stream(nghttp2_session *session,
+                                                 nghttp2_frame *frame,
+                                                 uint32_t error_code) {
+  int rv;
+  rv = session_handle_invalid_stream(session, frame, error_code);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+  return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+}
+
+/*
+ * Handles invalid frame which causes connection error.
+ */
+static int session_handle_invalid_connection(nghttp2_session *session,
+                                             nghttp2_frame *frame,
+                                             uint32_t error_code,
+                                             const char *reason) {
+  if (session->callbacks.on_invalid_frame_recv_callback) {
+    if (session->callbacks.on_invalid_frame_recv_callback(
+            session, frame, error_code, session->user_data) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+  return nghttp2_session_terminate_session_with_reason(session, error_code,
+                                                       reason);
+}
+
+static int session_inflate_handle_invalid_connection(nghttp2_session *session,
+                                                     nghttp2_frame *frame,
+                                                     uint32_t error_code,
+                                                     const char *reason) {
+  int rv;
+  rv = session_handle_invalid_connection(session, frame, error_code, reason);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+  return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+}
+
+/*
+ * Inflates header block in the memory pointed by |in| with |inlen|
+ * bytes. If this function returns NGHTTP2_ERR_PAUSE, the caller must
+ * call this function again, until it returns 0 or one of negative
+ * error code.  If |call_header_cb| is zero, the on_header_callback
+ * are not invoked and the function never return NGHTTP2_ERR_PAUSE. If
+ * the given |in| is the last chunk of header block, the |final| must
+ * be nonzero. If header block is successfully processed (which is
+ * indicated by the return value 0, NGHTTP2_ERR_PAUSE or
+ * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE), the number of processed
+ * input bytes is assigned to the |*readlen_ptr|.
+ *
+ * This function return 0 if it succeeds, or one of the negative error
+ * codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
+ *     The callback returns this error code, indicating that this
+ *     stream should be RST_STREAMed.
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_PAUSE
+ *     The callback function returned NGHTTP2_ERR_PAUSE
+ * NGHTTP2_ERR_HEADER_COMP
+ *     Header decompression failed
+ */
+static int inflate_header_block(nghttp2_session *session, nghttp2_frame *frame,
+                                size_t *readlen_ptr, uint8_t *in, size_t inlen,
+                                int final, int call_header_cb) {
+  ssize_t proclen;
+  int rv;
+  int inflate_flags;
+  nghttp2_nv nv;
+  nghttp2_stream *stream;
+
+  *readlen_ptr = 0;
+
+  DEBUGF(fprintf(stderr, "recv: decoding header block %zu bytes\n", inlen));
+  for (;;) {
+    inflate_flags = 0;
+    proclen = nghttp2_hd_inflate_hd(&session->hd_inflater, &nv, &inflate_flags,
+                                    in, inlen, final);
+    if (nghttp2_is_fatal((int)proclen)) {
+      return (int)proclen;
+    }
+    if (proclen < 0) {
+      if (session->iframe.state == NGHTTP2_IB_READ_HEADER_BLOCK) {
+        stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+
+        if (stream && stream->state != NGHTTP2_STREAM_CLOSING) {
+          /* Adding RST_STREAM here is very important. It prevents
+             from invoking subsequent callbacks for the same stream
+             ID. */
+          rv = nghttp2_session_add_rst_stream(session, frame->hd.stream_id,
+                                              NGHTTP2_COMPRESSION_ERROR);
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+      }
+      rv =
+          nghttp2_session_terminate_session(session, NGHTTP2_COMPRESSION_ERROR);
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      return NGHTTP2_ERR_HEADER_COMP;
+    }
+    in += proclen;
+    inlen -= proclen;
+    *readlen_ptr += proclen;
+
+    DEBUGF(fprintf(stderr, "recv: proclen=%zd\n", proclen));
+
+    if (call_header_cb && (inflate_flags & NGHTTP2_HD_INFLATE_EMIT)) {
+      rv = session_call_on_header(session, frame, &nv);
+      /* This handles NGHTTP2_ERR_PAUSE and
+         NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE as well */
+      if (rv != 0) {
+        return rv;
+      }
+    }
+    if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+      nghttp2_hd_inflate_end_headers(&session->hd_inflater);
+      break;
+    }
+    if ((inflate_flags & NGHTTP2_HD_INFLATE_EMIT) == 0 && inlen == 0) {
+      break;
+    }
+  }
+  return 0;
+}
+
+/*
+ * Decompress header blocks of incoming request HEADERS and also call
+ * additional callbacks. This function can be called again if this
+ * function returns NGHTTP2_ERR_PAUSE.
+ *
+ * This function returns 0 if it succeeds, or one of negative error
+ * codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_end_request_headers_received(nghttp2_session *session _U_,
+                                                 nghttp2_frame *frame,
+                                                 nghttp2_stream *stream) {
+  if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+    nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+  }
+  /* Here we assume that stream is not shutdown in NGHTTP2_SHUT_WR */
+  return 0;
+}
+
+/*
+ * Decompress header blocks of incoming (push-)response HEADERS and
+ * also call additional callbacks. This function can be called again
+ * if this function returns NGHTTP2_ERR_PAUSE.
+ *
+ * This function returns 0 if it succeeds, or one of negative error
+ * codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_end_response_headers_received(nghttp2_session *session,
+                                                  nghttp2_frame *frame,
+                                                  nghttp2_stream *stream) {
+  int rv;
+  if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+    /* This is the last frame of this stream, so disallow
+       further receptions. */
+    nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+    rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+  return 0;
+}
+
+/*
+ * Decompress header blocks of incoming HEADERS and also call
+ * additional callbacks. This function can be called again if this
+ * function returns NGHTTP2_ERR_PAUSE.
+ *
+ * This function returns 0 if it succeeds, or one of negative error
+ * codes:
+ *
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_end_headers_received(nghttp2_session *session,
+                                         nghttp2_frame *frame,
+                                         nghttp2_stream *stream) {
+  int rv;
+  if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+    if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+    }
+    nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+    rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+  return 0;
+}
+
+static int session_after_header_block_received(nghttp2_session *session) {
+  int rv;
+  nghttp2_frame *frame = &session->iframe.frame;
+  nghttp2_stream *stream;
+
+  /* We don't call on_frame_recv_callback if stream has been closed
+     already or being closed. */
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+  if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) {
+    return 0;
+  }
+
+  rv = session_call_on_frame_received(session, frame);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+
+  if (frame->hd.type != NGHTTP2_HEADERS) {
+    return 0;
+  }
+
+  switch (frame->headers.cat) {
+  case NGHTTP2_HCAT_REQUEST:
+    return nghttp2_session_end_request_headers_received(session, frame, stream);
+  case NGHTTP2_HCAT_RESPONSE:
+  case NGHTTP2_HCAT_PUSH_RESPONSE:
+    return nghttp2_session_end_response_headers_received(session, frame,
+                                                         stream);
+  case NGHTTP2_HCAT_HEADERS:
+    return nghttp2_session_end_headers_received(session, frame, stream);
+  default:
+    assert(0);
+  }
+  return 0;
+}
+
+int nghttp2_session_on_request_headers_received(nghttp2_session *session,
+                                                nghttp2_frame *frame) {
+  int rv = 0;
+  nghttp2_stream *stream;
+  if (frame->hd.stream_id == 0) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "request HEADERS: stream_id == 0");
+  }
+
+  /* If client recieves idle stream from server, it is invalid
+     regardless stream ID is even or odd.  This is because client is
+     not expected to receive request from server. */
+  if (!session->server) {
+    if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+      return session_inflate_handle_invalid_connection(
+          session, frame, NGHTTP2_PROTOCOL_ERROR,
+          "request HEADERS: client received request");
+    }
+
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+
+  if (!session_is_new_peer_stream_id(session, frame->hd.stream_id)) {
+    /* The spec says if an endpoint receives a HEADERS with invalid
+       stream ID, it MUST issue connection error with error code
+       PROTOCOL_ERROR.  But we could get trailer HEADERS after we have
+       sent RST_STREAM to this stream and peer have not received it.
+       Then connection error is too harsh.  It means that we only use
+       connection error if stream ID refers idle stream.  OTherwise we
+       just ignore HEADERS for now. */
+    if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+      return session_inflate_handle_invalid_connection(
+          session, frame, NGHTTP2_PROTOCOL_ERROR,
+          "request HEADERS: invalid stream_id");
+    }
+
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+  session->last_recv_stream_id = frame->hd.stream_id;
+
+  if (session->goaway_flags & NGHTTP2_GOAWAY_SENT) {
+    /* We just ignore stream after GOAWAY was queued */
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+
+  if (session_is_incoming_concurrent_streams_max(session)) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "request HEADERS: max concurrent streams exceeded");
+  }
+
+  if (frame->headers.pri_spec.stream_id == frame->hd.stream_id) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "request HEADERS: depend on itself");
+  }
+
+  if (session_is_incoming_concurrent_streams_pending_max(session)) {
+    return session_inflate_handle_invalid_stream(session, frame,
+                                                 NGHTTP2_REFUSED_STREAM);
+  }
+
+  stream = nghttp2_session_open_stream(
+      session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE,
+      &frame->headers.pri_spec, NGHTTP2_STREAM_OPENING, NULL);
+  if (!stream) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+  session->last_proc_stream_id = session->last_recv_stream_id;
+  rv = session_call_on_begin_headers(session, frame);
+  if (rv != 0) {
+    return rv;
+  }
+  return 0;
+}
+
+int nghttp2_session_on_response_headers_received(nghttp2_session *session,
+                                                 nghttp2_frame *frame,
+                                                 nghttp2_stream *stream) {
+  int rv;
+  /* This function is only called if stream->state ==
+     NGHTTP2_STREAM_OPENING and stream_id is local side initiated. */
+  assert(stream->state == NGHTTP2_STREAM_OPENING &&
+         nghttp2_session_is_my_stream_id(session, frame->hd.stream_id));
+  if (frame->hd.stream_id == 0) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "response HEADERS: stream_id == 0");
+  }
+  if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+    /* half closed (remote): from the spec:
+
+       If an endpoint receives additional frames for a stream that is
+       in this state it MUST respond with a stream error (Section
+       5.4.2) of type STREAM_CLOSED.
+    */
+    return session_inflate_handle_invalid_stream(session, frame,
+                                                 NGHTTP2_STREAM_CLOSED);
+  }
+  stream->state = NGHTTP2_STREAM_OPENED;
+  rv = session_call_on_begin_headers(session, frame);
+  if (rv != 0) {
+    return rv;
+  }
+  return 0;
+}
+
+int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
+                                                      nghttp2_frame *frame,
+                                                      nghttp2_stream *stream) {
+  int rv = 0;
+  assert(stream->state == NGHTTP2_STREAM_RESERVED);
+  if (frame->hd.stream_id == 0) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "push response HEADERS: stream_id == 0");
+  }
+  if (session->goaway_flags) {
+    /* We don't accept new stream after GOAWAY is sent or received. */
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+
+  if (session_is_incoming_concurrent_streams_max(session)) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "push response HEADERS: max concurrent streams exceeded");
+  }
+  if (session_is_incoming_concurrent_streams_pending_max(session)) {
+    return session_inflate_handle_invalid_stream(session, frame,
+                                                 NGHTTP2_REFUSED_STREAM);
+  }
+
+  nghttp2_stream_promise_fulfilled(stream);
+  ++session->num_incoming_streams;
+  rv = session_call_on_begin_headers(session, frame);
+  if (rv != 0) {
+    return rv;
+  }
+  return 0;
+}
+
+int nghttp2_session_on_headers_received(nghttp2_session *session,
+                                        nghttp2_frame *frame,
+                                        nghttp2_stream *stream) {
+  int rv = 0;
+  if (frame->hd.stream_id == 0) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "HEADERS: stream_id == 0");
+  }
+  if (stream->state == NGHTTP2_STREAM_RESERVED) {
+    /* reserved. The valid push response HEADERS is processed by
+       nghttp2_session_on_push_response_headers_received(). This
+       generic HEADERS is called invalid cases for HEADERS against
+       reserved state. */
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "HEADERS: stream in reserved");
+  }
+  if ((stream->shut_flags & NGHTTP2_SHUT_RD)) {
+    /* half closed (remote): from the spec:
+
+       If an endpoint receives additional frames for a stream that is
+       in this state it MUST respond with a stream error (Section
+       5.4.2) of type STREAM_CLOSED.
+    */
+    return session_inflate_handle_invalid_stream(session, frame,
+                                                 NGHTTP2_STREAM_CLOSED);
+  }
+  if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+    if (stream->state == NGHTTP2_STREAM_OPENED) {
+      rv = session_call_on_begin_headers(session, frame);
+      if (rv != 0) {
+        return rv;
+      }
+      return 0;
+    } else if (stream->state == NGHTTP2_STREAM_CLOSING) {
+      /* This is race condition. NGHTTP2_STREAM_CLOSING indicates
+         that we queued RST_STREAM but it has not been sent. It will
+         eventually sent, so we just ignore this frame. */
+      return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+    } else {
+      return session_inflate_handle_invalid_stream(session, frame,
+                                                   NGHTTP2_PROTOCOL_ERROR);
+    }
+  }
+  /* If this is remote peer initiated stream, it is OK unless it
+     has sent END_STREAM frame already. But if stream is in
+     NGHTTP2_STREAM_CLOSING, we discard the frame. This is a race
+     condition. */
+  if (stream->state != NGHTTP2_STREAM_CLOSING) {
+    rv = session_call_on_begin_headers(session, frame);
+    if (rv != 0) {
+      return rv;
+    }
+    return 0;
+  }
+  return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+}
+
+static int session_process_headers_frame(nghttp2_session *session) {
+  int rv;
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+  nghttp2_stream *stream;
+
+  rv = nghttp2_frame_unpack_headers_payload(&frame->headers, iframe->sbuf.pos,
+                                            nghttp2_buf_len(&iframe->sbuf));
+
+  if (rv != 0) {
+    return nghttp2_session_terminate_session_with_reason(
+        session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: could not unpack");
+  }
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+  if (!stream) {
+    frame->headers.cat = NGHTTP2_HCAT_REQUEST;
+    return nghttp2_session_on_request_headers_received(session, frame);
+  }
+
+  if (nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+    if (stream->state == NGHTTP2_STREAM_OPENING) {
+      frame->headers.cat = NGHTTP2_HCAT_RESPONSE;
+      return nghttp2_session_on_response_headers_received(session, frame,
+                                                          stream);
+    }
+    frame->headers.cat = NGHTTP2_HCAT_HEADERS;
+    return nghttp2_session_on_headers_received(session, frame, stream);
+  }
+  if (stream->state == NGHTTP2_STREAM_RESERVED) {
+    frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE;
+    return nghttp2_session_on_push_response_headers_received(session, frame,
+                                                             stream);
+  }
+  frame->headers.cat = NGHTTP2_HCAT_HEADERS;
+  return nghttp2_session_on_headers_received(session, frame, stream);
+}
+
+int nghttp2_session_on_priority_received(nghttp2_session *session,
+                                         nghttp2_frame *frame) {
+  int rv;
+  nghttp2_stream *stream;
+
+  if (frame->hd.stream_id == 0) {
+    return session_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream_id == 0");
+  }
+
+  if (!session->server) {
+    /* Re-prioritization works only in server */
+    return session_call_on_frame_received(session, frame);
+  }
+
+  stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
+
+  if (!stream) {
+    /* PRIORITY against idle stream can create anchor node in
+       dependency tree. */
+    if (!session_detect_idle_stream(session, frame->hd.stream_id)) {
+      return 0;
+    }
+
+    stream = nghttp2_session_open_stream(
+        session, frame->hd.stream_id, NGHTTP2_STREAM_FLAG_NONE,
+        &frame->priority.pri_spec, NGHTTP2_STREAM_IDLE, NULL);
+
+    if (stream == NULL) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+  } else {
+    rv = nghttp2_session_reprioritize_stream(session, stream,
+                                             &frame->priority.pri_spec);
+
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+
+  return session_call_on_frame_received(session, frame);
+}
+
+static int session_process_priority_frame(nghttp2_session *session) {
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+
+  nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos,
+                                        nghttp2_buf_len(&iframe->sbuf));
+
+  return nghttp2_session_on_priority_received(session, frame);
+}
+
+int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
+                                           nghttp2_frame *frame) {
+  int rv;
+  nghttp2_stream *stream;
+  if (frame->hd.stream_id == 0) {
+    return session_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "RST_STREAM: stream_id == 0");
+  }
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+  if (!stream) {
+    if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+      return session_handle_invalid_connection(
+          session, frame, NGHTTP2_PROTOCOL_ERROR, "RST_STREAM: stream in idle");
+    }
+  }
+
+  rv = session_call_on_frame_received(session, frame);
+  if (rv != 0) {
+    return rv;
+  }
+  rv = nghttp2_session_close_stream(session, frame->hd.stream_id,
+                                    frame->rst_stream.error_code);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+  return 0;
+}
+
+static int session_process_rst_stream_frame(nghttp2_session *session) {
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+
+  nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, iframe->sbuf.pos,
+                                          nghttp2_buf_len(&iframe->sbuf));
+
+  return nghttp2_session_on_rst_stream_received(session, frame);
+}
+
+static int update_remote_initial_window_size_func(nghttp2_map_entry *entry,
+                                                  void *ptr) {
+  int rv;
+  nghttp2_update_window_size_arg *arg;
+  nghttp2_stream *stream;
+
+  arg = (nghttp2_update_window_size_arg *)ptr;
+  stream = (nghttp2_stream *)entry;
+
+  rv = nghttp2_stream_update_remote_initial_window_size(
+      stream, arg->new_window_size, arg->old_window_size);
+  if (rv != 0) {
+    return nghttp2_session_terminate_session(arg->session,
+                                             NGHTTP2_FLOW_CONTROL_ERROR);
+  }
+
+  /* If window size gets positive, push deferred DATA frame to
+     outbound queue. */
+  if (stream->remote_window_size > 0 &&
+      nghttp2_stream_check_deferred_by_flow_control(stream)) {
+
+    rv = nghttp2_stream_resume_deferred_item(
+        stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL, arg->session);
+
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+  return 0;
+}
+
+/*
+ * Updates the remote initial window size of all active streams.  If
+ * error occurs, all streams may not be updated.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int
+session_update_remote_initial_window_size(nghttp2_session *session,
+                                          int32_t new_initial_window_size) {
+  nghttp2_update_window_size_arg arg;
+
+  arg.session = session;
+  arg.new_window_size = new_initial_window_size;
+  arg.old_window_size = session->remote_settings.initial_window_size;
+
+  return nghttp2_map_each(&session->streams,
+                          update_remote_initial_window_size_func, &arg);
+}
+
+static int update_local_initial_window_size_func(nghttp2_map_entry *entry,
+                                                 void *ptr) {
+  int rv;
+  nghttp2_update_window_size_arg *arg;
+  nghttp2_stream *stream;
+  arg = (nghttp2_update_window_size_arg *)ptr;
+  stream = (nghttp2_stream *)entry;
+  rv = nghttp2_stream_update_local_initial_window_size(
+      stream, arg->new_window_size, arg->old_window_size);
+  if (rv != 0) {
+    return nghttp2_session_terminate_session(arg->session,
+                                             NGHTTP2_FLOW_CONTROL_ERROR);
+  }
+  if (!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
+
+    if (nghttp2_should_send_window_update(stream->local_window_size,
+                                          stream->recv_window_size)) {
+
+      rv = nghttp2_session_add_window_update(arg->session, NGHTTP2_FLAG_NONE,
+                                             stream->stream_id,
+                                             stream->recv_window_size);
+      if (rv != 0) {
+        return rv;
+      }
+      stream->recv_window_size = 0;
+    }
+  }
+  return 0;
+}
+
+/*
+ * Updates the local initial window size of all active streams.  If
+ * error occurs, all streams may not be updated.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int
+session_update_local_initial_window_size(nghttp2_session *session,
+                                         int32_t new_initial_window_size,
+                                         int32_t old_initial_window_size) {
+  nghttp2_update_window_size_arg arg;
+  arg.session = session;
+  arg.new_window_size = new_initial_window_size;
+  arg.old_window_size = old_initial_window_size;
+  return nghttp2_map_each(&session->streams,
+                          update_local_initial_window_size_func, &arg);
+}
+
+/*
+ * Apply SETTINGS values |iv| having |niv| elements to the local
+ * settings.  We assumes that all values in |iv| is correct, since we
+ * validated them in nghttp2_session_add_settings() already.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_HEADER_COMP
+ *     The header table size is out of range
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_session_update_local_settings(nghttp2_session *session,
+                                          nghttp2_settings_entry *iv,
+                                          size_t niv) {
+  int rv;
+  size_t i;
+  int32_t new_initial_window_size = -1;
+  int32_t header_table_size = -1;
+  uint8_t header_table_size_seen = 0;
+  /* Use the value last seen. */
+  for (i = 0; i < niv; ++i) {
+    switch (iv[i].settings_id) {
+    case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+      header_table_size_seen = 1;
+      header_table_size = iv[i].value;
+      break;
+    case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+      new_initial_window_size = iv[i].value;
+      break;
+    }
+  }
+  if (header_table_size_seen) {
+    rv = nghttp2_hd_inflate_change_table_size(&session->hd_inflater,
+                                              header_table_size);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+  if (new_initial_window_size != -1) {
+    rv = session_update_local_initial_window_size(
+        session, new_initial_window_size,
+        session->local_settings.initial_window_size);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  for (i = 0; i < niv; ++i) {
+    switch (iv[i].settings_id) {
+    case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+      session->local_settings.header_table_size = iv[i].value;
+      break;
+    case NGHTTP2_SETTINGS_ENABLE_PUSH:
+      session->local_settings.enable_push = iv[i].value;
+      break;
+    case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+      session->local_settings.max_concurrent_streams = iv[i].value;
+      break;
+    case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+      session->local_settings.initial_window_size = iv[i].value;
+      break;
+    case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+      session->local_settings.max_frame_size = iv[i].value;
+      break;
+    case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+      session->local_settings.max_header_list_size = iv[i].value;
+      break;
+    }
+  }
+
+  session->pending_local_max_concurrent_stream =
+      NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+
+  return 0;
+}
+
+int nghttp2_session_on_settings_received(nghttp2_session *session,
+                                         nghttp2_frame *frame, int noack) {
+  int rv;
+  size_t i;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (frame->hd.stream_id != 0) {
+    return session_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "SETTINGS: stream_id != 0");
+  }
+  if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+    if (frame->settings.niv != 0) {
+      return session_handle_invalid_connection(
+          session, frame, NGHTTP2_FRAME_SIZE_ERROR,
+          "SETTINGS: ACK and payload != 0");
+    }
+    if (session->inflight_niv == -1) {
+      return session_handle_invalid_connection(
+          session, frame, NGHTTP2_PROTOCOL_ERROR, "SETTINGS: unexpected ACK");
+    }
+    rv = nghttp2_session_update_local_settings(session, session->inflight_iv,
+                                               session->inflight_niv);
+    nghttp2_mem_free(mem, session->inflight_iv);
+    session->inflight_iv = NULL;
+    session->inflight_niv = -1;
+    if (rv != 0) {
+      uint32_t error_code = NGHTTP2_INTERNAL_ERROR;
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+      if (rv == NGHTTP2_ERR_HEADER_COMP) {
+        error_code = NGHTTP2_COMPRESSION_ERROR;
+      }
+      return session_handle_invalid_connection(session, frame, error_code,
+                                               NULL);
+    }
+    return session_call_on_frame_received(session, frame);
+  }
+
+  for (i = 0; i < frame->settings.niv; ++i) {
+    nghttp2_settings_entry *entry = &frame->settings.iv[i];
+
+    switch (entry->settings_id) {
+    case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+
+      if (entry->value > NGHTTP2_MAX_HEADER_TABLE_SIZE) {
+        return session_handle_invalid_connection(
+            session, frame, NGHTTP2_COMPRESSION_ERROR,
+            "SETTINGS: too large SETTINGS_HEADER_TABLE_SIZE");
+      }
+
+      rv = nghttp2_hd_deflate_change_table_size(&session->hd_deflater,
+                                                entry->value);
+      if (rv != 0) {
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        } else {
+          return session_handle_invalid_connection(
+              session, frame, NGHTTP2_COMPRESSION_ERROR, NULL);
+        }
+      }
+
+      session->remote_settings.header_table_size = entry->value;
+
+      break;
+    case NGHTTP2_SETTINGS_ENABLE_PUSH:
+
+      if (entry->value != 0 && entry->value != 1) {
+        return session_handle_invalid_connection(
+            session, frame, NGHTTP2_PROTOCOL_ERROR,
+            "SETTINGS: invalid SETTINGS_ENBLE_PUSH");
+      }
+
+      if (!session->server && entry->value != 0) {
+        return session_handle_invalid_connection(
+            session, frame, NGHTTP2_PROTOCOL_ERROR,
+            "SETTINGS: server attempted to enable push");
+      }
+
+      session->remote_settings.enable_push = entry->value;
+
+      break;
+    case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+
+      session->remote_settings.max_concurrent_streams = entry->value;
+
+      break;
+    case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+
+      /* Update the initial window size of the all active streams */
+      /* Check that initial_window_size < (1u << 31) */
+      if (entry->value > NGHTTP2_MAX_WINDOW_SIZE) {
+        return session_handle_invalid_connection(
+            session, frame, NGHTTP2_FLOW_CONTROL_ERROR,
+            "SETTINGS: too large SETTINGS_INITIAL_WINDOW_SIZE");
+      }
+
+      rv = session_update_remote_initial_window_size(session, entry->value);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      if (rv != 0) {
+        return session_handle_invalid_connection(
+            session, frame, NGHTTP2_FLOW_CONTROL_ERROR, NULL);
+      }
+
+      session->remote_settings.initial_window_size = entry->value;
+
+      break;
+    case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+
+      if (entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN ||
+          entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) {
+        return session_handle_invalid_connection(
+            session, frame, NGHTTP2_PROTOCOL_ERROR,
+            "SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE");
+      }
+
+      session->remote_settings.max_frame_size = entry->value;
+
+      break;
+    case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+
+      session->remote_settings.max_header_list_size = entry->value;
+
+      break;
+    }
+  }
+
+  if (!noack && !session_is_closing(session)) {
+    rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0);
+
+    if (rv != 0) {
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      return session_handle_invalid_connection(session, frame,
+                                               NGHTTP2_INTERNAL_ERROR, NULL);
+    }
+  }
+
+  return session_call_on_frame_received(session, frame);
+}
+
+static int session_process_settings_frame(nghttp2_session *session) {
+  int rv;
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+  size_t i;
+  nghttp2_settings_entry min_header_size_entry;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  min_header_size_entry = iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1];
+
+  if (min_header_size_entry.value < UINT32_MAX) {
+    /* If we have less value, then we must have
+       SETTINGS_HEADER_TABLE_SIZE in i < iframe->niv */
+    for (i = 0; i < iframe->niv; ++i) {
+      if (iframe->iv[i].settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE) {
+        break;
+      }
+    }
+
+    assert(i < iframe->niv);
+
+    if (min_header_size_entry.value != iframe->iv[i].value) {
+      iframe->iv[iframe->niv++] = iframe->iv[i];
+      iframe->iv[i] = min_header_size_entry;
+    }
+  }
+
+  rv = nghttp2_frame_unpack_settings_payload(&frame->settings, iframe->iv,
+                                             iframe->niv, mem);
+  if (rv != 0) {
+    assert(nghttp2_is_fatal(rv));
+    return rv;
+  }
+  return nghttp2_session_on_settings_received(session, frame, 0 /* ACK */);
+}
+
+int nghttp2_session_on_push_promise_received(nghttp2_session *session,
+                                             nghttp2_frame *frame) {
+  int rv;
+  nghttp2_stream *stream;
+  nghttp2_stream *promised_stream;
+  nghttp2_priority_spec pri_spec;
+
+  if (frame->hd.stream_id == 0) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "PUSH_PROMISE: stream_id == 0");
+  }
+  if (session->server || session->local_settings.enable_push == 0) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "PUSH_PROMISE: push disabled");
+  }
+  if (session->goaway_flags) {
+    /* We just dicard PUSH_PROMISE after GOAWAY is sent or
+       received. */
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+
+  if (!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) {
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "PUSH_PROMISE: invalid stream_id");
+  }
+
+  if (!session_is_new_peer_stream_id(session,
+                                     frame->push_promise.promised_stream_id)) {
+    /* The spec says if an endpoint receives a PUSH_PROMISE with
+       illegal stream ID is subject to a connection error of type
+       PROTOCOL_ERROR. */
+    return session_inflate_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "PUSH_PROMISE: invalid promised_stream_id");
+  }
+  session->last_recv_stream_id = frame->push_promise.promised_stream_id;
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+  if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) {
+    if (!stream) {
+      if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+        return session_inflate_handle_invalid_connection(
+            session, frame, NGHTTP2_PROTOCOL_ERROR,
+            "PUSH_PROMISE: stream in idle");
+      }
+    }
+    rv = nghttp2_session_add_rst_stream(session,
+                                        frame->push_promise.promised_stream_id,
+                                        NGHTTP2_REFUSED_STREAM);
+    if (rv != 0) {
+      return rv;
+    }
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+  if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+    if (session->callbacks.on_invalid_frame_recv_callback) {
+      if (session->callbacks.on_invalid_frame_recv_callback(
+              session, frame, NGHTTP2_PROTOCOL_ERROR, session->user_data) !=
+          0) {
+        return NGHTTP2_ERR_CALLBACK_FAILURE;
+      }
+    }
+    rv = nghttp2_session_add_rst_stream(session,
+                                        frame->push_promise.promised_stream_id,
+                                        NGHTTP2_PROTOCOL_ERROR);
+    if (rv != 0) {
+      return rv;
+    }
+    return NGHTTP2_ERR_IGN_HEADER_BLOCK;
+  }
+
+  /* TODO It is unclear reserved stream dpeneds on associated
+     stream with or without exclusive flag set */
+  nghttp2_priority_spec_init(&pri_spec, stream->stream_id,
+                             NGHTTP2_DEFAULT_WEIGHT, 0);
+
+  promised_stream = nghttp2_session_open_stream(
+      session, frame->push_promise.promised_stream_id, NGHTTP2_STREAM_FLAG_PUSH,
+      &pri_spec, NGHTTP2_STREAM_RESERVED, NULL);
+
+  if (!promised_stream) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  session->last_proc_stream_id = session->last_recv_stream_id;
+  rv = session_call_on_begin_headers(session, frame);
+  if (rv != 0) {
+    return rv;
+  }
+  return 0;
+}
+
+static int session_process_push_promise_frame(nghttp2_session *session) {
+  int rv;
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+
+  rv = nghttp2_frame_unpack_push_promise_payload(
+      &frame->push_promise, iframe->sbuf.pos, nghttp2_buf_len(&iframe->sbuf));
+
+  if (rv != 0) {
+    return nghttp2_session_terminate_session_with_reason(
+        session, NGHTTP2_PROTOCOL_ERROR, "PUSH_PROMISE: could not unpack");
+  }
+
+  return nghttp2_session_on_push_promise_received(session, frame);
+}
+
+int nghttp2_session_on_ping_received(nghttp2_session *session,
+                                     nghttp2_frame *frame) {
+  int rv = 0;
+  if (frame->hd.stream_id != 0) {
+    return session_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "PING: stream_id != 0");
+  }
+  if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0 &&
+      !session_is_closing(session)) {
+    /* Peer sent ping, so ping it back */
+    rv = nghttp2_session_add_ping(session, NGHTTP2_FLAG_ACK,
+                                  frame->ping.opaque_data);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+  return session_call_on_frame_received(session, frame);
+}
+
+static int session_process_ping_frame(nghttp2_session *session) {
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+
+  nghttp2_frame_unpack_ping_payload(&frame->ping, iframe->sbuf.pos,
+                                    nghttp2_buf_len(&iframe->sbuf));
+
+  return nghttp2_session_on_ping_received(session, frame);
+}
+
+int nghttp2_session_on_goaway_received(nghttp2_session *session,
+                                       nghttp2_frame *frame) {
+  int rv;
+
+  if (frame->hd.stream_id != 0) {
+    return session_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR, "GOAWAY: stream_id != 0");
+  }
+  /* Spec says Endpoints MUST NOT increase the value they send in the
+     last stream identifier. */
+  if ((frame->goaway.last_stream_id > 0 &&
+       !nghttp2_session_is_my_stream_id(session,
+                                        frame->goaway.last_stream_id)) ||
+      session->remote_last_stream_id < frame->goaway.last_stream_id) {
+    return session_handle_invalid_connection(session, frame,
+                                             NGHTTP2_PROTOCOL_ERROR,
+                                             "GOAWAY: invalid last_stream_id");
+  }
+
+  session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
+
+  session->remote_last_stream_id = frame->goaway.last_stream_id;
+
+  rv = session_call_on_frame_received(session, frame);
+
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+
+  return session_close_stream_on_goaway(session, frame->goaway.last_stream_id,
+                                        0);
+}
+
+static int session_process_goaway_frame(nghttp2_session *session) {
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+
+  nghttp2_frame_unpack_goaway_payload(
+      &frame->goaway, iframe->sbuf.pos, nghttp2_buf_len(&iframe->sbuf),
+      iframe->lbuf.pos, nghttp2_buf_len(&iframe->lbuf));
+
+  nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0);
+
+  return nghttp2_session_on_goaway_received(session, frame);
+}
+
+static int
+session_on_connection_window_update_received(nghttp2_session *session,
+                                             nghttp2_frame *frame) {
+  /* Handle connection-level flow control */
+  if (frame->window_update.window_size_increment == 0) {
+    return session_handle_invalid_connection(session, frame,
+                                             NGHTTP2_PROTOCOL_ERROR, NULL);
+  }
+
+  if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
+      session->remote_window_size) {
+    return session_handle_invalid_connection(session, frame,
+                                             NGHTTP2_FLOW_CONTROL_ERROR, NULL);
+  }
+  session->remote_window_size += frame->window_update.window_size_increment;
+
+  return session_call_on_frame_received(session, frame);
+}
+
+static int session_on_stream_window_update_received(nghttp2_session *session,
+                                                    nghttp2_frame *frame) {
+  int rv;
+  nghttp2_stream *stream;
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+  if (!stream) {
+    if (session_detect_idle_stream(session, frame->hd.stream_id)) {
+      return session_handle_invalid_connection(session, frame,
+                                               NGHTTP2_PROTOCOL_ERROR,
+                                               "WINDOW_UPDATE to idle stream");
+    }
+    return 0;
+  }
+  if (state_reserved_remote(session, stream)) {
+    return session_handle_invalid_connection(
+        session, frame, NGHTTP2_PROTOCOL_ERROR,
+        "WINDOW_UPADATE to reserved stream");
+  }
+  if (frame->window_update.window_size_increment == 0) {
+    return session_handle_invalid_stream(session, frame,
+                                         NGHTTP2_PROTOCOL_ERROR);
+  }
+  if (NGHTTP2_MAX_WINDOW_SIZE - frame->window_update.window_size_increment <
+      stream->remote_window_size) {
+    return session_handle_invalid_stream(session, frame,
+                                         NGHTTP2_FLOW_CONTROL_ERROR);
+  }
+  stream->remote_window_size += frame->window_update.window_size_increment;
+
+  if (stream->remote_window_size > 0 &&
+      nghttp2_stream_check_deferred_by_flow_control(stream)) {
+
+    rv = nghttp2_stream_resume_deferred_item(
+        stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL, session);
+
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+  return session_call_on_frame_received(session, frame);
+}
+
+int nghttp2_session_on_window_update_received(nghttp2_session *session,
+                                              nghttp2_frame *frame) {
+  if (frame->hd.stream_id == 0) {
+    return session_on_connection_window_update_received(session, frame);
+  } else {
+    return session_on_stream_window_update_received(session, frame);
+  }
+}
+
+static int session_process_window_update_frame(nghttp2_session *session) {
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  nghttp2_frame *frame = &iframe->frame;
+
+  nghttp2_frame_unpack_window_update_payload(
+      &frame->window_update, iframe->sbuf.pos, nghttp2_buf_len(&iframe->sbuf));
+
+  return nghttp2_session_on_window_update_received(session, frame);
+}
+
+/* static int get_error_code_from_lib_error_code(int lib_error_code) */
+/* { */
+/*   switch(lib_error_code) { */
+/*   case NGHTTP2_ERR_HEADER_COMP: */
+/*     return NGHTTP2_COMPRESSION_ERROR; */
+/*   case NGHTTP2_ERR_FRAME_SIZE_ERROR: */
+/*     return NGHTTP2_FRAME_SIZE_ERROR; */
+/*   default: */
+/*     return NGHTTP2_PROTOCOL_ERROR; */
+/*   } */
+/* } */
+
+int nghttp2_session_on_data_received(nghttp2_session *session,
+                                     nghttp2_frame *frame) {
+  int rv = 0;
+  nghttp2_stream *stream;
+
+  /* We don't call on_frame_recv_callback if stream has been closed
+     already or being closed. */
+  stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+  if (!stream || stream->state == NGHTTP2_STREAM_CLOSING) {
+    /* This should be treated as stream error, but it results in lots
+       of RST_STREAM. So just ignore frame against nonexistent stream
+       for now. */
+    return 0;
+  }
+
+  rv = session_call_on_frame_received(session, frame);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+
+  if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+    nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+    rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream);
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+  return 0;
+}
+
+/* For errors, this function only returns FATAL error. */
+static int session_process_data_frame(nghttp2_session *session) {
+  int rv;
+  nghttp2_frame *public_data_frame = &session->iframe.frame;
+  rv = nghttp2_session_on_data_received(session, public_data_frame);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+  return 0;
+}
+
+/*
+ * Now we have SETTINGS synchronization, flow control error can be
+ * detected strictly. If DATA frame is received with length > 0 and
+ * current received window size + delta length is strictly larger than
+ * local window size, it is subject to FLOW_CONTROL_ERROR, so return
+ * -1. Note that local_window_size is calculated after SETTINGS ACK is
+ * received from peer, so peer must honor this limit. If the resulting
+ * recv_window_size is strictly larger than NGHTTP2_MAX_WINDOW_SIZE,
+ * return -1 too.
+ */
+static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta,
+                                   int32_t local_window_size) {
+  if (*recv_window_size_ptr > local_window_size - (int32_t)delta ||
+      *recv_window_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - (int32_t)delta) {
+    return -1;
+  }
+  *recv_window_size_ptr += delta;
+  return 0;
+}
+
+/*
+ * Accumulates received bytes |delta_size| for stream-level flow
+ * control and decides whether to send WINDOW_UPDATE to that stream.
+ * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
+ * be sent.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int session_update_recv_stream_window_size(nghttp2_session *session,
+                                                  nghttp2_stream *stream,
+                                                  size_t delta_size,
+                                                  int send_window_update) {
+  int rv;
+  rv = adjust_recv_window_size(&stream->recv_window_size, delta_size,
+                               stream->local_window_size);
+  if (rv != 0) {
+    return nghttp2_session_add_rst_stream(session, stream->stream_id,
+                                          NGHTTP2_FLOW_CONTROL_ERROR);
+  }
+  /* We don't have to send WINDOW_UPDATE if the data received is the
+     last chunk in the incoming stream. */
+  if (send_window_update &&
+      !(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
+    /* We have to use local_settings here because it is the constraint
+       the remote endpoint should honor. */
+    if (nghttp2_should_send_window_update(stream->local_window_size,
+                                          stream->recv_window_size)) {
+      rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE,
+                                             stream->stream_id,
+                                             stream->recv_window_size);
+      if (rv == 0) {
+        stream->recv_window_size = 0;
+      } else {
+        return rv;
+      }
+    }
+  }
+  return 0;
+}
+
+/*
+ * Accumulates received bytes |delta_size| for connection-level flow
+ * control and decides whether to send WINDOW_UPDATE to the
+ * connection.  If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
+ * WINDOW_UPDATE will not be sent.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int session_update_recv_connection_window_size(nghttp2_session *session,
+                                                      size_t delta_size) {
+  int rv;
+  rv = adjust_recv_window_size(&session->recv_window_size, delta_size,
+                               session->local_window_size);
+  if (rv != 0) {
+    return nghttp2_session_terminate_session(session,
+                                             NGHTTP2_FLOW_CONTROL_ERROR);
+  }
+  if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
+
+    if (nghttp2_should_send_window_update(session->local_window_size,
+                                          session->recv_window_size)) {
+      /* Use stream ID 0 to update connection-level flow control
+         window */
+      rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE, 0,
+                                             session->recv_window_size);
+      if (rv != 0) {
+        return rv;
+      }
+
+      session->recv_window_size = 0;
+    }
+  }
+  return 0;
+}
+
+static int session_update_consumed_size(nghttp2_session *session,
+                                        int32_t *consumed_size_ptr,
+                                        int32_t *recv_window_size_ptr,
+                                        int32_t stream_id, size_t delta_size,
+                                        int32_t local_window_size) {
+  int32_t recv_size;
+  int rv;
+
+  if ((size_t)*consumed_size_ptr > NGHTTP2_MAX_WINDOW_SIZE - delta_size) {
+    return nghttp2_session_terminate_session(session,
+                                             NGHTTP2_FLOW_CONTROL_ERROR);
+  }
+
+  *consumed_size_ptr += delta_size;
+
+  /* recv_window_size may be smaller than consumed_size, because it
+     may be decreased by negative value with
+     nghttp2_submit_window_update(). */
+  recv_size = nghttp2_min(*consumed_size_ptr, *recv_window_size_ptr);
+
+  if (nghttp2_should_send_window_update(local_window_size, recv_size)) {
+    rv = nghttp2_session_add_window_update(session, NGHTTP2_FLAG_NONE,
+                                           stream_id, recv_size);
+
+    if (rv != 0) {
+      return rv;
+    }
+
+    *recv_window_size_ptr -= recv_size;
+    *consumed_size_ptr -= recv_size;
+  }
+
+  return 0;
+}
+
+static int session_update_stream_consumed_size(nghttp2_session *session,
+                                               nghttp2_stream *stream,
+                                               size_t delta_size) {
+  return session_update_consumed_size(
+      session, &stream->consumed_size, &stream->recv_window_size,
+      stream->stream_id, delta_size, stream->local_window_size);
+}
+
+static int session_update_connection_consumed_size(nghttp2_session *session,
+                                                   size_t delta_size) {
+  return session_update_consumed_size(session, &session->consumed_size,
+                                      &session->recv_window_size, 0, delta_size,
+                                      session->local_window_size);
+}
+
+/*
+ * Checks that we can receive the DATA frame for stream, which is
+ * indicated by |session->iframe.frame.hd.stream_id|. If it is a
+ * connection error situation, GOAWAY frame will be issued by this
+ * function.
+ *
+ * If the DATA frame is allowed, returns 0.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_IGN_PAYLOAD
+ *   The reception of DATA frame is connection error; or should be
+ *   ignored.
+ * NGHTTP2_ERR_NOMEM
+ *   Out of memory.
+ */
+static int session_on_data_received_fail_fast(nghttp2_session *session) {
+  int rv;
+  nghttp2_stream *stream;
+  nghttp2_inbound_frame *iframe;
+  int32_t stream_id;
+  const char *failure_reason;
+  uint32_t error_code = NGHTTP2_PROTOCOL_ERROR;
+
+  iframe = &session->iframe;
+  stream_id = iframe->frame.hd.stream_id;
+
+  if (stream_id == 0) {
+    /* The spec says that if a DATA frame is received whose stream ID
+       is 0, the recipient MUST respond with a connection error of
+       type PROTOCOL_ERROR. */
+    failure_reason = "DATA: stream_id == 0";
+    goto fail;
+  }
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (!stream) {
+    if (session_detect_idle_stream(session, stream_id)) {
+      failure_reason = "DATA: stream in idle";
+      error_code = NGHTTP2_STREAM_CLOSED;
+      goto fail;
+    }
+    return NGHTTP2_ERR_IGN_PAYLOAD;
+  }
+  if (stream->shut_flags & NGHTTP2_SHUT_RD) {
+    failure_reason = "DATA: stream in half-closed(remote)";
+    error_code = NGHTTP2_STREAM_CLOSED;
+    goto fail;
+  }
+
+  if (nghttp2_session_is_my_stream_id(session, stream_id)) {
+    if (stream->state == NGHTTP2_STREAM_CLOSING) {
+      return NGHTTP2_ERR_IGN_PAYLOAD;
+    }
+    if (stream->state != NGHTTP2_STREAM_OPENED) {
+      failure_reason = "DATA: stream not opened";
+      goto fail;
+    }
+    return 0;
+  }
+  if (stream->state == NGHTTP2_STREAM_RESERVED) {
+    failure_reason = "DATA: stream in reserved";
+    goto fail;
+  }
+  if (stream->state == NGHTTP2_STREAM_CLOSING) {
+    return NGHTTP2_ERR_IGN_PAYLOAD;
+  }
+  return 0;
+fail:
+  rv = nghttp2_session_terminate_session_with_reason(session, error_code,
+                                                     failure_reason);
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+  return NGHTTP2_ERR_IGN_PAYLOAD;
+}
+
+static size_t inbound_frame_payload_readlen(nghttp2_inbound_frame *iframe,
+                                            const uint8_t *in,
+                                            const uint8_t *last) {
+  return nghttp2_min((size_t)(last - in), iframe->payloadleft);
+}
+
+/*
+ * Resets iframe->sbuf and advance its mark pointer by |left| bytes.
+ */
+static void inbound_frame_set_mark(nghttp2_inbound_frame *iframe, size_t left) {
+  nghttp2_buf_reset(&iframe->sbuf);
+  iframe->sbuf.mark += left;
+}
+
+static size_t inbound_frame_buf_read(nghttp2_inbound_frame *iframe,
+                                     const uint8_t *in, const uint8_t *last) {
+  size_t readlen;
+
+  readlen = nghttp2_min(last - in, nghttp2_buf_mark_avail(&iframe->sbuf));
+
+  iframe->sbuf.last = nghttp2_cpymem(iframe->sbuf.last, in, readlen);
+
+  return readlen;
+}
+
+/*
+ * Unpacks SETTINGS entry in iframe->sbuf.
+ */
+static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) {
+  nghttp2_settings_entry iv;
+  size_t i;
+
+  nghttp2_frame_unpack_settings_entry(&iv, iframe->sbuf.pos);
+
+  switch (iv.settings_id) {
+  case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+  case NGHTTP2_SETTINGS_ENABLE_PUSH:
+  case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+  case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+  case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+  case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+    break;
+  default:
+    DEBUGF(fprintf(stderr, "recv: ignore unknown settings id=0x%02x\n",
+                   iv.settings_id));
+    return;
+  }
+
+  for (i = 0; i < iframe->niv; ++i) {
+    if (iframe->iv[i].settings_id == iv.settings_id) {
+      iframe->iv[i] = iv;
+      break;
+    }
+  }
+
+  if (i == iframe->niv) {
+    iframe->iv[iframe->niv++] = iv;
+  }
+
+  if (iv.settings_id == NGHTTP2_SETTINGS_HEADER_TABLE_SIZE &&
+      iv.value < iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1].value) {
+
+    iframe->iv[NGHTTP2_INBOUND_NUM_IV - 1] = iv;
+  }
+}
+
+/*
+ * Checks PADDED flags and set iframe->sbuf to read them accordingly.
+ * If padding is set, this function returns 1.  If no padding is set,
+ * this function returns 0.  On error, returns -1.
+ */
+static int inbound_frame_handle_pad(nghttp2_inbound_frame *iframe,
+                                    nghttp2_frame_hd *hd) {
+  if (hd->flags & NGHTTP2_FLAG_PADDED) {
+    if (hd->length < 1) {
+      return -1;
+    }
+    inbound_frame_set_mark(iframe, 1);
+    return 1;
+  }
+  DEBUGF(fprintf(stderr, "recv: no padding in payload\n"));
+  return 0;
+}
+
+/*
+ * Computes number of padding based on flags. This function returns
+ * the calculated length if it succeeds, or -1.
+ */
+static ssize_t inbound_frame_compute_pad(nghttp2_inbound_frame *iframe) {
+  size_t padlen;
+
+  /* 1 for Pad Length field */
+  padlen = iframe->sbuf.pos[0] + 1;
+
+  DEBUGF(fprintf(stderr, "recv: padlen=%zu\n", padlen));
+
+  /* We cannot use iframe->frame.hd.length because of CONTINUATION */
+  if (padlen - 1 > iframe->payloadleft) {
+    return -1;
+  }
+
+  iframe->padlen = padlen;
+
+  return padlen;
+}
+
+/*
+ * This function returns the effective payload length in the data of
+ * length |readlen| when the remaning payload is |payloadleft|. The
+ * |payloadleft| does not include |readlen|. If padding was started
+ * strictly before this data chunk, this function returns -1.
+ */
+static ssize_t inbound_frame_effective_readlen(nghttp2_inbound_frame *iframe,
+                                               size_t payloadleft,
+                                               size_t readlen) {
+  size_t trail_padlen =
+      nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen);
+
+  if (trail_padlen > payloadleft) {
+    size_t padlen;
+    padlen = trail_padlen - payloadleft;
+    if (readlen < padlen) {
+      return -1;
+    } else {
+      return readlen - padlen;
+    }
+  }
+  return readlen;
+}
+
+ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
+                                 size_t inlen) {
+  const uint8_t *first = in, *last = in + inlen;
+  nghttp2_inbound_frame *iframe = &session->iframe;
+  size_t readlen;
+  ssize_t padlen;
+  int rv;
+  int busy = 0;
+  nghttp2_frame_hd cont_hd;
+  nghttp2_stream *stream;
+  size_t pri_fieldlen;
+  nghttp2_mem *mem;
+
+  DEBUGF(fprintf(stderr,
+                 "recv: connection recv_window_size=%d, local_window=%d\n",
+                 session->recv_window_size, session->local_window_size));
+
+  mem = &session->mem;
+
+  for (;;) {
+    switch (iframe->state) {
+    case NGHTTP2_IB_READ_CLIENT_PREFACE:
+      readlen = nghttp2_min(inlen, iframe->payloadleft);
+
+      if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
+                     NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN -
+                     iframe->payloadleft,
+                 in, readlen) != 0) {
+        return NGHTTP2_ERR_BAD_PREFACE;
+      }
+
+      iframe->payloadleft -= readlen;
+      in += readlen;
+
+      if (iframe->payloadleft == 0) {
+        session_inbound_frame_reset(session);
+        iframe->state = NGHTTP2_IB_READ_FIRST_SETTINGS;
+      }
+
+      break;
+    case NGHTTP2_IB_READ_FIRST_SETTINGS:
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_FIRST_SETTINGS]\n"));
+
+      readlen = inbound_frame_buf_read(iframe, in, last);
+      in += readlen;
+
+      if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+        return in - first;
+      }
+
+      if (iframe->sbuf.pos[3] != NGHTTP2_SETTINGS ||
+          (iframe->sbuf.pos[4] & NGHTTP2_FLAG_ACK)) {
+        nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->sbuf.pos);
+        iframe->payloadleft = iframe->frame.hd.length;
+
+        busy = 1;
+
+        iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+        rv = nghttp2_session_terminate_session_with_reason(
+            session, NGHTTP2_PROTOCOL_ERROR, "SETTINGS expected");
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        break;
+      }
+
+      iframe->state = NGHTTP2_IB_READ_HEAD;
+
+    /* Fall through */
+    case NGHTTP2_IB_READ_HEAD: {
+      int on_begin_frame_called = 0;
+
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_HEAD]\n"));
+
+      readlen = inbound_frame_buf_read(iframe, in, last);
+      in += readlen;
+
+      if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+        return in - first;
+      }
+
+      nghttp2_frame_unpack_frame_hd(&iframe->frame.hd, iframe->sbuf.pos);
+      iframe->payloadleft = iframe->frame.hd.length;
+
+      DEBUGF(fprintf(stderr, "recv: payloadlen=%zu, type=%u, flags=0x%02x, "
+                             "stream_id=%d\n",
+                     iframe->frame.hd.length, iframe->frame.hd.type,
+                     iframe->frame.hd.flags, iframe->frame.hd.stream_id));
+
+      if (iframe->frame.hd.length > session->local_settings.max_frame_size) {
+        DEBUGF(fprintf(stderr, "recv: length is too large %zu > %u\n",
+                       iframe->frame.hd.length,
+                       session->local_settings.max_frame_size));
+
+        busy = 1;
+
+        iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+        rv = nghttp2_session_terminate_session_with_reason(
+            session, NGHTTP2_FRAME_SIZE_ERROR, "too large frame size");
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        break;
+      }
+
+      switch (iframe->frame.hd.type) {
+      case NGHTTP2_DATA: {
+        DEBUGF(fprintf(stderr, "recv: DATA\n"));
+
+        iframe->frame.hd.flags &=
+            (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PADDED);
+        /* Check stream is open. If it is not open or closing,
+           ignore payload. */
+        busy = 1;
+
+        rv = session_on_data_received_fail_fast(session);
+        if (rv == NGHTTP2_ERR_IGN_PAYLOAD) {
+          DEBUGF(fprintf(stderr, "recv: DATA not allowed stream_id=%d\n",
+                         iframe->frame.hd.stream_id));
+          iframe->state = NGHTTP2_IB_IGN_DATA;
+          break;
+        }
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
+        if (rv < 0) {
+          iframe->state = NGHTTP2_IB_IGN_DATA;
+          rv = nghttp2_session_terminate_session_with_reason(
+              session, NGHTTP2_PROTOCOL_ERROR,
+              "DATA: insufficient padding space");
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+          break;
+        }
+
+        if (rv == 1) {
+          iframe->state = NGHTTP2_IB_READ_PAD_DATA;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_DATA;
+        break;
+      }
+      case NGHTTP2_HEADERS:
+
+        DEBUGF(fprintf(stderr, "recv: HEADERS\n"));
+
+        iframe->frame.hd.flags &=
+            (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS |
+             NGHTTP2_FLAG_PADDED | NGHTTP2_FLAG_PRIORITY);
+
+        rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
+        if (rv < 0) {
+          busy = 1;
+
+          iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+          rv = nghttp2_session_terminate_session_with_reason(
+              session, NGHTTP2_PROTOCOL_ERROR,
+              "HEADERS: insufficient padding space");
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+          break;
+        }
+
+        if (rv == 1) {
+          iframe->state = NGHTTP2_IB_READ_NBYTE;
+          break;
+        }
+
+        pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags);
+
+        if (pri_fieldlen > 0) {
+          if (iframe->payloadleft < pri_fieldlen) {
+            busy = 1;
+            iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+            break;
+          }
+
+          iframe->state = NGHTTP2_IB_READ_NBYTE;
+
+          inbound_frame_set_mark(iframe, pri_fieldlen);
+
+          break;
+        }
+
+        /* Call on_begin_frame_callback here because
+           session_process_headers_frame() may call
+           on_begin_headers_callback */
+        rv = session_call_on_begin_frame(session, &iframe->frame.hd);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        on_begin_frame_called = 1;
+
+        rv = session_process_headers_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        busy = 1;
+
+        if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) {
+          iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK;
+
+        break;
+      case NGHTTP2_PRIORITY:
+        DEBUGF(fprintf(stderr, "recv: PRIORITY\n"));
+
+        iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
+
+        if (iframe->payloadleft != NGHTTP2_PRIORITY_SPECLEN) {
+          busy = 1;
+
+          iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_NBYTE;
+
+        inbound_frame_set_mark(iframe, NGHTTP2_PRIORITY_SPECLEN);
+
+        break;
+      case NGHTTP2_RST_STREAM:
+      case NGHTTP2_WINDOW_UPDATE:
+#ifdef DEBUGBUILD
+        switch (iframe->frame.hd.type) {
+        case NGHTTP2_RST_STREAM:
+          DEBUGF(fprintf(stderr, "recv: RST_STREAM\n"));
+          break;
+        case NGHTTP2_WINDOW_UPDATE:
+          DEBUGF(fprintf(stderr, "recv: WINDOW_UPDATE\n"));
+          break;
+        }
+#endif /* DEBUGBUILD */
+
+        iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
+
+        if (iframe->payloadleft != 4) {
+          busy = 1;
+          iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_NBYTE;
+
+        inbound_frame_set_mark(iframe, 4);
+
+        break;
+      case NGHTTP2_SETTINGS:
+        DEBUGF(fprintf(stderr, "recv: SETTINGS\n"));
+
+        iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK;
+
+        if ((iframe->frame.hd.length % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) ||
+            ((iframe->frame.hd.flags & NGHTTP2_FLAG_ACK) &&
+             iframe->payloadleft > 0)) {
+          busy = 1;
+          iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_SETTINGS;
+
+        if (iframe->payloadleft) {
+          inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH);
+          break;
+        }
+
+        busy = 1;
+
+        inbound_frame_set_mark(iframe, 0);
+
+        break;
+      case NGHTTP2_PUSH_PROMISE:
+        DEBUGF(fprintf(stderr, "recv: PUSH_PROMISE\n"));
+
+        iframe->frame.hd.flags &=
+            (NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PADDED);
+
+        rv = inbound_frame_handle_pad(iframe, &iframe->frame.hd);
+        if (rv < 0) {
+          busy = 1;
+          iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+          rv = nghttp2_session_terminate_session_with_reason(
+              session, NGHTTP2_PROTOCOL_ERROR,
+              "PUSH_PROMISE: insufficient padding space");
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+          break;
+        }
+
+        if (rv == 1) {
+          iframe->state = NGHTTP2_IB_READ_NBYTE;
+          break;
+        }
+
+        if (iframe->payloadleft < 4) {
+          busy = 1;
+          iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_NBYTE;
+
+        inbound_frame_set_mark(iframe, 4);
+
+        break;
+      case NGHTTP2_PING:
+        DEBUGF(fprintf(stderr, "recv: PING\n"));
+
+        iframe->frame.hd.flags &= NGHTTP2_FLAG_ACK;
+
+        if (iframe->payloadleft != 8) {
+          busy = 1;
+          iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_NBYTE;
+        inbound_frame_set_mark(iframe, 8);
+
+        break;
+      case NGHTTP2_GOAWAY:
+        DEBUGF(fprintf(stderr, "recv: GOAWAY\n"));
+
+        iframe->frame.hd.flags = NGHTTP2_FLAG_NONE;
+
+        if (iframe->payloadleft < 8) {
+          busy = 1;
+          iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_NBYTE;
+        inbound_frame_set_mark(iframe, 8);
+
+        break;
+      case NGHTTP2_CONTINUATION:
+        DEBUGF(fprintf(stderr, "recv: unexpected CONTINUATION\n"));
+
+        /* Receiving CONTINUATION in this state are subject to
+           connection error of type PROTOCOL_ERROR */
+        rv = nghttp2_session_terminate_session_with_reason(
+            session, NGHTTP2_PROTOCOL_ERROR, "CONTINUATION: unexpected");
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        busy = 1;
+
+        iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+        break;
+      default:
+        DEBUGF(fprintf(stderr, "recv: unknown frame\n"));
+
+        /* Silently ignore unknown frame type. */
+
+        busy = 1;
+
+        iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+        break;
+      }
+
+      if (!on_begin_frame_called) {
+        switch (iframe->state) {
+        case NGHTTP2_IB_IGN_HEADER_BLOCK:
+        case NGHTTP2_IB_IGN_PAYLOAD:
+        case NGHTTP2_IB_FRAME_SIZE_ERROR:
+        case NGHTTP2_IB_IGN_DATA:
+          break;
+        default:
+          rv = session_call_on_begin_frame(session, &iframe->frame.hd);
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+      }
+
+      break;
+    }
+    case NGHTTP2_IB_READ_NBYTE:
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_NBYTE]\n"));
+
+      readlen = inbound_frame_buf_read(iframe, in, last);
+      in += readlen;
+      iframe->payloadleft -= readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu, left=%zd\n",
+                     readlen, iframe->payloadleft,
+                     nghttp2_buf_mark_avail(&iframe->sbuf)));
+
+      if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+        return in - first;
+      }
+
+      switch (iframe->frame.hd.type) {
+      case NGHTTP2_HEADERS:
+        if (iframe->padlen == 0 &&
+            (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) {
+          padlen = inbound_frame_compute_pad(iframe);
+          if (padlen < 0) {
+            busy = 1;
+            rv = nghttp2_session_terminate_session_with_reason(
+                session, NGHTTP2_PROTOCOL_ERROR, "HEADERS: invalid padding");
+            if (nghttp2_is_fatal(rv)) {
+              return rv;
+            }
+            iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+            break;
+          }
+          iframe->frame.headers.padlen = padlen;
+
+          pri_fieldlen = nghttp2_frame_priority_len(iframe->frame.hd.flags);
+
+          if (pri_fieldlen > 0) {
+            if (iframe->payloadleft < pri_fieldlen) {
+              busy = 1;
+              iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+              break;
+            }
+            iframe->state = NGHTTP2_IB_READ_NBYTE;
+            inbound_frame_set_mark(iframe, pri_fieldlen);
+            break;
+          } else {
+            /* Truncate buffers used for padding spec */
+            inbound_frame_set_mark(iframe, 0);
+          }
+        }
+
+        rv = session_process_headers_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        busy = 1;
+
+        if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) {
+          iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK;
+
+        break;
+      case NGHTTP2_PRIORITY:
+        rv = session_process_priority_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        session_inbound_frame_reset(session);
+
+        break;
+      case NGHTTP2_RST_STREAM:
+        rv = session_process_rst_stream_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        session_inbound_frame_reset(session);
+
+        break;
+      case NGHTTP2_PUSH_PROMISE:
+        if (iframe->padlen == 0 &&
+            (iframe->frame.hd.flags & NGHTTP2_FLAG_PADDED)) {
+          padlen = inbound_frame_compute_pad(iframe);
+          if (padlen < 0) {
+            busy = 1;
+            rv = nghttp2_session_terminate_session_with_reason(
+                session, NGHTTP2_PROTOCOL_ERROR,
+                "PUSH_PROMISE: invalid padding");
+            if (nghttp2_is_fatal(rv)) {
+              return rv;
+            }
+            iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+            break;
+          }
+
+          iframe->frame.push_promise.padlen = padlen;
+
+          if (iframe->payloadleft < 4) {
+            busy = 1;
+            iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR;
+            break;
+          }
+
+          iframe->state = NGHTTP2_IB_READ_NBYTE;
+
+          inbound_frame_set_mark(iframe, 4);
+
+          break;
+        }
+
+        rv = session_process_push_promise_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        busy = 1;
+
+        if (rv == NGHTTP2_ERR_IGN_HEADER_BLOCK) {
+          iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+          break;
+        }
+
+        iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK;
+
+        break;
+      case NGHTTP2_PING:
+        rv = session_process_ping_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        session_inbound_frame_reset(session);
+
+        break;
+      case NGHTTP2_GOAWAY: {
+        size_t debuglen;
+
+        /* 8 is Last-stream-ID + Error Code */
+        debuglen = iframe->frame.hd.length - 8;
+
+        if (debuglen > 0) {
+          iframe->raw_lbuf = nghttp2_mem_malloc(mem, debuglen);
+
+          if (iframe->raw_lbuf == NULL) {
+            return NGHTTP2_ERR_NOMEM;
+          }
+
+          nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, debuglen);
+        }
+
+        busy = 1;
+
+        iframe->state = NGHTTP2_IB_READ_GOAWAY_DEBUG;
+
+        break;
+      }
+      case NGHTTP2_WINDOW_UPDATE:
+        rv = session_process_window_update_frame(session);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        session_inbound_frame_reset(session);
+
+        break;
+      default:
+        /* This is unknown frame */
+        session_inbound_frame_reset(session);
+
+        break;
+      }
+      break;
+    case NGHTTP2_IB_READ_HEADER_BLOCK:
+    case NGHTTP2_IB_IGN_HEADER_BLOCK: {
+      ssize_t data_readlen;
+#ifdef DEBUGBUILD
+      if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) {
+        fprintf(stderr, "recv: [IB_READ_HEADER_BLOCK]\n");
+      } else {
+        fprintf(stderr, "recv: [IB_IGN_HEADER_BLOCK]\n");
+      }
+#endif /* DEBUGBUILD */
+
+      readlen = inbound_frame_payload_readlen(iframe, in, last);
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
+                     iframe->payloadleft - readlen));
+
+      data_readlen = inbound_frame_effective_readlen(
+          iframe, iframe->payloadleft - readlen, readlen);
+      if (data_readlen >= 0) {
+        size_t trail_padlen;
+        size_t hd_proclen = 0;
+        trail_padlen =
+            nghttp2_frame_trail_padlen(&iframe->frame, iframe->padlen);
+        DEBUGF(fprintf(stderr, "recv: block final=%d\n",
+                       (iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) &&
+                           iframe->payloadleft - data_readlen == trail_padlen));
+
+        rv = inflate_header_block(
+            session, &iframe->frame, &hd_proclen, (uint8_t *)in, data_readlen,
+            (iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) &&
+                iframe->payloadleft - data_readlen == trail_padlen,
+            iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        if (rv == NGHTTP2_ERR_PAUSE) {
+          in += hd_proclen;
+          iframe->payloadleft -= hd_proclen;
+
+          return in - first;
+        }
+
+        if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+          /* The application says no more headers. We decompress the
+             rest of the header block but not invoke on_header_callback
+             and on_frame_recv_callback. */
+          in += hd_proclen;
+          iframe->payloadleft -= hd_proclen;
+
+          rv = nghttp2_session_add_rst_stream(
+              session, iframe->frame.hd.stream_id, NGHTTP2_INTERNAL_ERROR);
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+          busy = 1;
+          iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+          break;
+        }
+
+        in += readlen;
+        iframe->payloadleft -= readlen;
+
+        if (rv == NGHTTP2_ERR_HEADER_COMP) {
+          /* GOAWAY is already issued */
+          if (iframe->payloadleft == 0) {
+            session_inbound_frame_reset(session);
+          } else {
+            busy = 1;
+            iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+          }
+          break;
+        }
+      } else {
+        in += readlen;
+        iframe->payloadleft -= readlen;
+      }
+
+      if (iframe->payloadleft) {
+        break;
+      }
+
+      if ((iframe->frame.hd.flags & NGHTTP2_FLAG_END_HEADERS) == 0) {
+
+        inbound_frame_set_mark(iframe, NGHTTP2_FRAME_HDLEN);
+
+        iframe->padlen = 0;
+
+        if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) {
+          iframe->state = NGHTTP2_IB_EXPECT_CONTINUATION;
+        } else {
+          iframe->state = NGHTTP2_IB_IGN_CONTINUATION;
+        }
+      } else {
+        if (iframe->state == NGHTTP2_IB_READ_HEADER_BLOCK) {
+          rv = session_after_header_block_received(session);
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+        session_inbound_frame_reset(session);
+      }
+      break;
+    }
+    case NGHTTP2_IB_IGN_PAYLOAD:
+      DEBUGF(fprintf(stderr, "recv: [IB_IGN_PAYLOAD]\n"));
+
+      readlen = inbound_frame_payload_readlen(iframe, in, last);
+      iframe->payloadleft -= readlen;
+      in += readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
+                     iframe->payloadleft));
+
+      if (iframe->payloadleft) {
+        break;
+      }
+
+      switch (iframe->frame.hd.type) {
+      case NGHTTP2_HEADERS:
+      case NGHTTP2_PUSH_PROMISE:
+      case NGHTTP2_CONTINUATION:
+        /* Mark inflater bad so that we won't perform further decoding */
+        session->hd_inflater.ctx.bad = 1;
+        break;
+      default:
+        break;
+      }
+
+      session_inbound_frame_reset(session);
+
+      break;
+    case NGHTTP2_IB_FRAME_SIZE_ERROR:
+      DEBUGF(fprintf(stderr, "recv: [IB_FRAME_SIZE_ERROR]\n"));
+
+      rv = session_handle_frame_size_error(session, &iframe->frame);
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      busy = 1;
+
+      iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+      break;
+    case NGHTTP2_IB_READ_SETTINGS:
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_SETTINGS]\n"));
+
+      readlen = inbound_frame_buf_read(iframe, in, last);
+      iframe->payloadleft -= readlen;
+      in += readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
+                     iframe->payloadleft));
+
+      if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+        break;
+      }
+
+      if (readlen > 0) {
+        inbound_frame_set_settings_entry(iframe);
+      }
+      if (iframe->payloadleft) {
+        inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH);
+        break;
+      }
+
+      rv = session_process_settings_frame(session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      session_inbound_frame_reset(session);
+
+      break;
+    case NGHTTP2_IB_READ_GOAWAY_DEBUG:
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_GOAWAY_DEBUG]\n"));
+
+      readlen = inbound_frame_payload_readlen(iframe, in, last);
+
+      iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen);
+
+      iframe->payloadleft -= readlen;
+      in += readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
+                     iframe->payloadleft));
+
+      if (iframe->payloadleft) {
+        assert(nghttp2_buf_avail(&iframe->lbuf) > 0);
+
+        break;
+      }
+
+      rv = session_process_goaway_frame(session);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      session_inbound_frame_reset(session);
+
+      break;
+    case NGHTTP2_IB_EXPECT_CONTINUATION:
+    case NGHTTP2_IB_IGN_CONTINUATION:
+#ifdef DEBUGBUILD
+      if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) {
+        fprintf(stderr, "recv: [IB_EXPECT_CONTINUATION]\n");
+      } else {
+        fprintf(stderr, "recv: [IB_IGN_CONTINUATION]\n");
+      }
+#endif /* DEBUGBUILD */
+
+      readlen = inbound_frame_buf_read(iframe, in, last);
+      in += readlen;
+
+      if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+        return in - first;
+      }
+
+      nghttp2_frame_unpack_frame_hd(&cont_hd, iframe->sbuf.pos);
+      iframe->payloadleft = cont_hd.length;
+
+      DEBUGF(fprintf(stderr, "recv: payloadlen=%zu, type=%u, flags=0x%02x, "
+                             "stream_id=%d\n",
+                     cont_hd.length, cont_hd.type, cont_hd.flags,
+                     cont_hd.stream_id));
+
+      if (cont_hd.type != NGHTTP2_CONTINUATION ||
+          cont_hd.stream_id != iframe->frame.hd.stream_id) {
+        DEBUGF(fprintf(stderr, "recv: expected stream_id=%d, type=%d, but "
+                               "got stream_id=%d, type=%d\n",
+                       iframe->frame.hd.stream_id, NGHTTP2_CONTINUATION,
+                       cont_hd.stream_id, cont_hd.type));
+        rv = nghttp2_session_terminate_session_with_reason(
+            session, NGHTTP2_PROTOCOL_ERROR,
+            "unexpected non-CONTINUATION frame or stream_id is invalid");
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        busy = 1;
+
+        iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
+
+        break;
+      }
+
+      /* CONTINUATION won't bear NGHTTP2_PADDED flag */
+
+      iframe->frame.hd.flags |= cont_hd.flags & NGHTTP2_FLAG_END_HEADERS;
+      iframe->frame.hd.length += cont_hd.length;
+
+      busy = 1;
+
+      if (iframe->state == NGHTTP2_IB_EXPECT_CONTINUATION) {
+        iframe->state = NGHTTP2_IB_READ_HEADER_BLOCK;
+
+        rv = session_call_on_begin_frame(session, &cont_hd);
+
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      } else {
+        iframe->state = NGHTTP2_IB_IGN_HEADER_BLOCK;
+      }
+
+      break;
+    case NGHTTP2_IB_READ_PAD_DATA:
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_PAD_DATA]\n"));
+
+      readlen = inbound_frame_buf_read(iframe, in, last);
+      in += readlen;
+      iframe->payloadleft -= readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu, left=%zu\n",
+                     readlen, iframe->payloadleft,
+                     nghttp2_buf_mark_avail(&iframe->sbuf)));
+
+      if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
+        return in - first;
+      }
+
+      /* Pad Length field is subject to flow control */
+      rv = session_update_recv_connection_window_size(session, readlen);
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      /* Pad Length field is consumed immediately */
+      rv =
+          nghttp2_session_consume(session, iframe->frame.hd.stream_id, readlen);
+
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id);
+      if (stream) {
+        rv = session_update_recv_stream_window_size(
+            session, stream, readlen,
+            iframe->payloadleft ||
+                (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+      }
+
+      busy = 1;
+
+      padlen = inbound_frame_compute_pad(iframe);
+      if (padlen < 0) {
+        rv = nghttp2_session_terminate_session_with_reason(
+            session, NGHTTP2_PROTOCOL_ERROR, "DATA: invalid padding");
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+        iframe->state = NGHTTP2_IB_IGN_DATA;
+        break;
+      }
+
+      iframe->frame.data.padlen = padlen;
+
+      iframe->state = NGHTTP2_IB_READ_DATA;
+
+      break;
+    case NGHTTP2_IB_READ_DATA:
+      DEBUGF(fprintf(stderr, "recv: [IB_READ_DATA]\n"));
+
+      readlen = inbound_frame_payload_readlen(iframe, in, last);
+      iframe->payloadleft -= readlen;
+      in += readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
+                     iframe->payloadleft));
+
+      if (readlen > 0) {
+        ssize_t data_readlen;
+
+        rv = session_update_recv_connection_window_size(session, readlen);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        stream =
+            nghttp2_session_get_stream(session, iframe->frame.hd.stream_id);
+        if (stream) {
+          rv = session_update_recv_stream_window_size(
+              session, stream, readlen,
+              iframe->payloadleft ||
+                  (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0);
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+
+        data_readlen = inbound_frame_effective_readlen(
+            iframe, iframe->payloadleft, readlen);
+
+        padlen = readlen - data_readlen;
+
+        if (padlen > 0) {
+          /* Padding is considered as "consumed" immediately */
+          rv = nghttp2_session_consume(session, iframe->frame.hd.stream_id,
+                                       padlen);
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+
+        DEBUGF(fprintf(stderr, "recv: data_readlen=%zd\n", data_readlen));
+
+        if (stream && data_readlen > 0 &&
+            session->callbacks.on_data_chunk_recv_callback) {
+          rv = session->callbacks.on_data_chunk_recv_callback(
+              session, iframe->frame.hd.flags, iframe->frame.hd.stream_id,
+              in - readlen, data_readlen, session->user_data);
+          if (rv == NGHTTP2_ERR_PAUSE) {
+            return in - first;
+          }
+
+          if (nghttp2_is_fatal(rv)) {
+            return NGHTTP2_ERR_CALLBACK_FAILURE;
+          }
+        }
+      }
+
+      if (iframe->payloadleft) {
+        break;
+      }
+
+      rv = session_process_data_frame(session);
+      if (nghttp2_is_fatal(rv)) {
+        return rv;
+      }
+
+      session_inbound_frame_reset(session);
+
+      break;
+    case NGHTTP2_IB_IGN_DATA:
+      DEBUGF(fprintf(stderr, "recv: [IB_IGN_DATA]\n"));
+
+      readlen = inbound_frame_payload_readlen(iframe, in, last);
+      iframe->payloadleft -= readlen;
+      in += readlen;
+
+      DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen,
+                     iframe->payloadleft));
+
+      if (readlen > 0) {
+        /* Update connection-level flow control window for ignored
+           DATA frame too */
+        rv = session_update_recv_connection_window_size(session, readlen);
+        if (nghttp2_is_fatal(rv)) {
+          return rv;
+        }
+
+        if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
+
+          /* Ignored DATA is considered as "consumed" immediately. */
+          rv = session_update_connection_consumed_size(session, readlen);
+
+          if (nghttp2_is_fatal(rv)) {
+            return rv;
+          }
+        }
+      }
+
+      if (iframe->payloadleft) {
+        break;
+      }
+
+      session_inbound_frame_reset(session);
+
+      break;
+    }
+
+    if (!busy && in == last) {
+      break;
+    }
+
+    busy = 0;
+  }
+
+  assert(in == last);
+
+  return in - first;
+}
+
+int nghttp2_session_recv(nghttp2_session *session) {
+  uint8_t buf[NGHTTP2_INBOUND_BUFFER_LENGTH];
+  while (1) {
+    ssize_t readlen;
+    readlen = session_recv(session, buf, sizeof(buf));
+    if (readlen > 0) {
+      ssize_t proclen = nghttp2_session_mem_recv(session, buf, readlen);
+      if (proclen < 0) {
+        return (int)proclen;
+      }
+      assert(proclen == readlen);
+    } else if (readlen == 0 || readlen == NGHTTP2_ERR_WOULDBLOCK) {
+      return 0;
+    } else if (readlen == NGHTTP2_ERR_EOF) {
+      return NGHTTP2_ERR_EOF;
+    } else if (readlen < 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+}
+
+/*
+ * Returns the number of active streams, which includes streams in
+ * reserved state.
+ */
+static size_t session_get_num_active_streams(nghttp2_session *session) {
+  return nghttp2_map_size(&session->streams) - session->num_closed_streams;
+}
+
+int nghttp2_session_want_read(nghttp2_session *session) {
+  size_t num_active_streams;
+
+  /* If this flag is set, we don't want to read. The application
+     should drop the connection. */
+  if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) {
+    return 0;
+  }
+
+  num_active_streams = session_get_num_active_streams(session);
+
+  /* Unless termination GOAWAY is sent or received, we always want to
+     read incoming frames. */
+
+  if (num_active_streams > 0) {
+    return 1;
+  }
+
+  /* If there is no active streams and GOAWAY has been sent or
+     received, we are done with this session. */
+  return (session->goaway_flags &
+          (NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0;
+}
+
+int nghttp2_session_want_write(nghttp2_session *session) {
+  size_t num_active_streams;
+
+  /* If these flag is set, we don't want to write any data. The
+     application should drop the connection. */
+  if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) {
+    return 0;
+  }
+
+  num_active_streams = session_get_num_active_streams(session);
+
+  /*
+   * Unless termination GOAWAY is sent or received, we want to write
+   * frames if there is pending ones. If pending frame is request/push
+   * response HEADERS and concurrent stream limit is reached, we don't
+   * want to write them.
+   */
+
+  if (session->aob.item == NULL && nghttp2_pq_empty(&session->ob_pq) &&
+      (nghttp2_pq_empty(&session->ob_da_pq) ||
+       session->remote_window_size == 0) &&
+      (nghttp2_pq_empty(&session->ob_ss_pq) ||
+       session_is_outgoing_concurrent_streams_max(session))) {
+    return 0;
+  }
+
+  if (num_active_streams > 0) {
+    return 1;
+  }
+
+  /* If there is no active streams and GOAWAY has been sent or
+     received, we are done with this session. */
+  return (session->goaway_flags &
+          (NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0;
+}
+
+int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,
+                             const uint8_t *opaque_data) {
+  int rv;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_ping_init(&frame->ping, flags, opaque_data);
+
+  rv = nghttp2_session_add_item(session, item);
+
+  if (rv != 0) {
+    nghttp2_frame_ping_free(&frame->ping);
+    nghttp2_mem_free(mem, item);
+    return rv;
+  }
+  return 0;
+}
+
+int nghttp2_session_add_goaway(nghttp2_session *session, int32_t last_stream_id,
+                               uint32_t error_code, const uint8_t *opaque_data,
+                               size_t opaque_data_len, uint8_t aux_flags) {
+  int rv;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  uint8_t *opaque_data_copy = NULL;
+  nghttp2_goaway_aux_data *aux_data;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (nghttp2_session_is_my_stream_id(session, last_stream_id)) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (opaque_data_len) {
+    if (opaque_data_len + 8 > NGHTTP2_MAX_PAYLOADLEN) {
+      return NGHTTP2_ERR_INVALID_ARGUMENT;
+    }
+    opaque_data_copy = nghttp2_mem_malloc(mem, opaque_data_len);
+    if (opaque_data_copy == NULL) {
+      return NGHTTP2_ERR_NOMEM;
+    }
+    memcpy(opaque_data_copy, opaque_data, opaque_data_len);
+  }
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    nghttp2_mem_free(mem, opaque_data_copy);
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  /* last_stream_id must not be increased from the value previously
+     sent */
+  last_stream_id = nghttp2_min(last_stream_id, session->local_last_stream_id);
+
+  nghttp2_frame_goaway_init(&frame->goaway, last_stream_id, error_code,
+                            opaque_data_copy, opaque_data_len);
+
+  aux_data = &item->aux_data.goaway;
+  aux_data->flags = aux_flags;
+
+  rv = nghttp2_session_add_item(session, item);
+  if (rv != 0) {
+    nghttp2_frame_goaway_free(&frame->goaway, mem);
+    nghttp2_mem_free(mem, item);
+    return rv;
+  }
+  return 0;
+}
+
+int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
+                                      int32_t stream_id,
+                                      int32_t window_size_increment) {
+  int rv;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_window_update_init(&frame->window_update, flags, stream_id,
+                                   window_size_increment);
+
+  rv = nghttp2_session_add_item(session, item);
+
+  if (rv != 0) {
+    nghttp2_frame_window_update_free(&frame->window_update);
+    nghttp2_mem_free(mem, item);
+    return rv;
+  }
+  return 0;
+}
+
+int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
+                                 const nghttp2_settings_entry *iv, size_t niv) {
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_settings_entry *iv_copy;
+  size_t i;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (flags & NGHTTP2_FLAG_ACK) {
+    if (niv != 0) {
+      return NGHTTP2_ERR_INVALID_ARGUMENT;
+    }
+  } else if (session->inflight_niv != -1) {
+    return NGHTTP2_ERR_TOO_MANY_INFLIGHT_SETTINGS;
+  }
+
+  if (!nghttp2_iv_check(iv, niv)) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  if (niv > 0) {
+    iv_copy = nghttp2_frame_iv_copy(iv, niv, mem);
+    if (iv_copy == NULL) {
+      nghttp2_mem_free(mem, item);
+      return NGHTTP2_ERR_NOMEM;
+    }
+  } else {
+    iv_copy = NULL;
+  }
+
+  if ((flags & NGHTTP2_FLAG_ACK) == 0) {
+    if (niv > 0) {
+      session->inflight_iv = nghttp2_frame_iv_copy(iv, niv, mem);
+
+      if (session->inflight_iv == NULL) {
+        nghttp2_mem_free(mem, iv_copy);
+        nghttp2_mem_free(mem, item);
+        return NGHTTP2_ERR_NOMEM;
+      }
+    } else {
+      session->inflight_iv = NULL;
+    }
+
+    session->inflight_niv = niv;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_settings_init(&frame->settings, flags, iv_copy, niv);
+  rv = nghttp2_session_add_item(session, item);
+  if (rv != 0) {
+    /* The only expected error is fatal one */
+    assert(nghttp2_is_fatal(rv));
+
+    if ((flags & NGHTTP2_FLAG_ACK) == 0) {
+      nghttp2_mem_free(mem, session->inflight_iv);
+      session->inflight_iv = NULL;
+      session->inflight_niv = -1;
+    }
+
+    nghttp2_frame_settings_free(&frame->settings, mem);
+    nghttp2_mem_free(mem, item);
+
+    return rv;
+  }
+
+  /* Extract NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS here and use
+     it to refuse the incoming streams with RST_STREAM. */
+  for (i = niv; i > 0; --i) {
+    if (iv[i - 1].settings_id == NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS) {
+      session->pending_local_max_concurrent_stream = iv[i - 1].value;
+      break;
+    }
+  }
+
+  return 0;
+}
+
+int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
+                              size_t datamax, nghttp2_frame *frame,
+                              nghttp2_data_aux_data *aux_data) {
+  int rv;
+  uint32_t data_flags;
+  ssize_t payloadlen;
+  ssize_t padded_payloadlen;
+  nghttp2_buf *buf;
+  size_t max_payloadlen;
+
+  assert(bufs->head == bufs->cur);
+
+  buf = &bufs->cur->buf;
+
+  if (session->callbacks.read_length_callback) {
+    nghttp2_stream *stream;
+
+    stream = nghttp2_session_get_stream(session, frame->hd.stream_id);
+    if (!stream) {
+      return NGHTTP2_ERR_INVALID_ARGUMENT;
+    }
+
+    payloadlen = session->callbacks.read_length_callback(
+        session, frame->hd.type, stream->stream_id, session->remote_window_size,
+        stream->remote_window_size, session->remote_settings.max_frame_size,
+        session->user_data);
+
+    DEBUGF(fprintf(stderr, "send: read_length_callback=%zd\n", payloadlen));
+
+    payloadlen = nghttp2_session_enforce_flow_control_limits(session, stream,
+                                                             payloadlen);
+
+    DEBUGF(fprintf(stderr,
+                   "send: read_length_callback after flow control=%zd\n",
+                   payloadlen));
+
+    if (payloadlen <= 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    if (payloadlen > nghttp2_buf_avail(buf)) {
+      /* Resize the current buffer(s).  The reason why we do +1 for
+         buffer size is for possible padding field. */
+      rv = nghttp2_bufs_realloc(&session->aob.framebufs,
+                                NGHTTP2_FRAME_HDLEN + 1 + payloadlen);
+
+      if (rv != 0) {
+        DEBUGF(fprintf(stderr, "send: realloc buffer failed rv=%d", rv));
+        /* If reallocation failed, old buffers are still in tact.  So
+           use safe limit. */
+        payloadlen = datamax;
+
+        DEBUGF(
+            fprintf(stderr, "send: use safe limit payloadlen=%zd", payloadlen));
+      } else {
+        assert(&session->aob.framebufs == bufs);
+
+        buf = &bufs->cur->buf;
+      }
+    }
+    datamax = (size_t)payloadlen;
+  }
+
+  /* Current max DATA length is less then buffer chunk size */
+  assert(nghttp2_buf_avail(buf) >= (ssize_t)datamax);
+
+  data_flags = NGHTTP2_DATA_FLAG_NONE;
+  payloadlen = aux_data->data_prd.read_callback(
+      session, frame->hd.stream_id, buf->pos, datamax, &data_flags,
+      &aux_data->data_prd.source, session->user_data);
+
+  if (payloadlen == NGHTTP2_ERR_DEFERRED ||
+      payloadlen == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
+    DEBUGF(fprintf(stderr, "send: DATA postponed due to %s\n",
+                   nghttp2_strerror((int)payloadlen)));
+
+    return (int)payloadlen;
+  }
+
+  if (payloadlen < 0 || datamax < (size_t)payloadlen) {
+    /* This is the error code when callback is failed. */
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  buf->last = buf->pos + payloadlen;
+  buf->pos -= NGHTTP2_FRAME_HDLEN;
+
+  /* Clear flags, because this may contain previous flags of previous
+     DATA */
+  frame->hd.flags = NGHTTP2_FLAG_NONE;
+
+  if (data_flags & NGHTTP2_DATA_FLAG_EOF) {
+    aux_data->eof = 1;
+    if (aux_data->flags & NGHTTP2_FLAG_END_STREAM) {
+      frame->hd.flags |= NGHTTP2_FLAG_END_STREAM;
+    }
+  }
+
+  frame->hd.length = payloadlen;
+  frame->data.padlen = 0;
+
+  max_payloadlen = nghttp2_min(datamax, frame->hd.length + NGHTTP2_MAX_PADLEN);
+
+  padded_payloadlen =
+      session_call_select_padding(session, frame, max_payloadlen);
+
+  if (nghttp2_is_fatal((int)padded_payloadlen)) {
+    return (int)padded_payloadlen;
+  }
+
+  frame->data.padlen = padded_payloadlen - payloadlen;
+
+  nghttp2_frame_pack_frame_hd(buf->pos, &frame->hd);
+
+  rv = nghttp2_frame_add_pad(bufs, &frame->hd, frame->data.padlen);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+void *nghttp2_session_get_stream_user_data(nghttp2_session *session,
+                                           int32_t stream_id) {
+  nghttp2_stream *stream;
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream) {
+    return stream->stream_user_data;
+  } else {
+    return NULL;
+  }
+}
+
+int nghttp2_session_set_stream_user_data(nghttp2_session *session,
+                                         int32_t stream_id,
+                                         void *stream_user_data) {
+  nghttp2_stream *stream;
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (!stream) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+  stream->stream_user_data = stream_user_data;
+  return 0;
+}
+
+int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id) {
+  int rv;
+  nghttp2_stream *stream;
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream == NULL || !nghttp2_stream_check_deferred_item(stream)) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  rv = nghttp2_stream_resume_deferred_item(
+      stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER, session);
+
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+
+  return rv;
+}
+
+size_t nghttp2_session_get_outbound_queue_size(nghttp2_session *session) {
+  return nghttp2_pq_size(&session->ob_pq) +
+         nghttp2_pq_size(&session->ob_ss_pq) +
+         nghttp2_pq_size(&session->ob_da_pq);
+}
+
+int32_t
+nghttp2_session_get_stream_effective_recv_data_length(nghttp2_session *session,
+                                                      int32_t stream_id) {
+  nghttp2_stream *stream;
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream == NULL) {
+    return -1;
+  }
+  return stream->recv_window_size < 0 ? 0 : stream->recv_window_size;
+}
+
+int32_t
+nghttp2_session_get_stream_effective_local_window_size(nghttp2_session *session,
+                                                       int32_t stream_id) {
+  nghttp2_stream *stream;
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream == NULL) {
+    return -1;
+  }
+  return stream->local_window_size;
+}
+
+int32_t
+nghttp2_session_get_effective_recv_data_length(nghttp2_session *session) {
+  return session->recv_window_size < 0 ? 0 : session->recv_window_size;
+}
+
+int32_t
+nghttp2_session_get_effective_local_window_size(nghttp2_session *session) {
+  return session->local_window_size;
+}
+
+int32_t nghttp2_session_get_stream_remote_window_size(nghttp2_session *session,
+                                                      int32_t stream_id) {
+  nghttp2_stream *stream;
+
+  stream = nghttp2_session_get_stream(session, stream_id);
+  if (stream == NULL) {
+    return -1;
+  }
+
+  /* stream->remote_window_size can be negative when
+     SETTINGS_INITIAL_WINDOW_SIZE is changed. */
+  return nghttp2_max(0, stream->remote_window_size);
+}
+
+int32_t nghttp2_session_get_remote_window_size(nghttp2_session *session) {
+  return session->remote_window_size;
+}
+
+uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
+                                             nghttp2_settings_id id) {
+  switch (id) {
+  case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+    return session->remote_settings.header_table_size;
+  case NGHTTP2_SETTINGS_ENABLE_PUSH:
+    return session->remote_settings.enable_push;
+  case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+    return session->remote_settings.max_concurrent_streams;
+  case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+    return session->remote_settings.initial_window_size;
+  case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+    return session->remote_settings.max_frame_size;
+  case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+    return session->remote_settings.max_header_list_size;
+  }
+
+  assert(0);
+}
+
+int nghttp2_session_upgrade(nghttp2_session *session,
+                            const uint8_t *settings_payload,
+                            size_t settings_payloadlen,
+                            void *stream_user_data) {
+  nghttp2_stream *stream;
+  nghttp2_frame frame;
+  nghttp2_settings_entry *iv;
+  size_t niv;
+  int rv;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if ((!session->server && session->next_stream_id != 1) ||
+      (session->server && session->last_recv_stream_id >= 1)) {
+    return NGHTTP2_ERR_PROTO;
+  }
+  if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+  rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload,
+                                              settings_payloadlen, mem);
+  if (rv != 0) {
+    return rv;
+  }
+
+  if (session->server) {
+    nghttp2_frame_hd_init(&frame.hd, settings_payloadlen, NGHTTP2_SETTINGS,
+                          NGHTTP2_FLAG_NONE, 0);
+    frame.settings.iv = iv;
+    frame.settings.niv = niv;
+    rv = nghttp2_session_on_settings_received(session, &frame, 1 /* No ACK */);
+  } else {
+    rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
+  }
+  nghttp2_mem_free(mem, iv);
+  if (rv != 0) {
+    return rv;
+  }
+
+  nghttp2_priority_spec_default_init(&pri_spec);
+
+  stream = nghttp2_session_open_stream(
+      session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec, NGHTTP2_STREAM_OPENING,
+      session->server ? NULL : stream_user_data);
+  if (stream == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+  if (session->server) {
+    nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+    session->last_recv_stream_id = 1;
+    session->last_proc_stream_id = 1;
+  } else {
+    nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+    session->next_stream_id += 2;
+  }
+  return 0;
+}
+
+int nghttp2_session_get_stream_local_close(nghttp2_session *session,
+                                           int32_t stream_id) {
+  nghttp2_stream *stream;
+
+  stream = nghttp2_session_get_stream(session, stream_id);
+
+  if (!stream) {
+    return -1;
+  }
+
+  return (stream->shut_flags & NGHTTP2_SHUT_WR) != 0;
+}
+
+int nghttp2_session_get_stream_remote_close(nghttp2_session *session,
+                                            int32_t stream_id) {
+  nghttp2_stream *stream;
+
+  stream = nghttp2_session_get_stream(session, stream_id);
+
+  if (!stream) {
+    return -1;
+  }
+
+  return (stream->shut_flags & NGHTTP2_SHUT_RD) != 0;
+}
+
+int nghttp2_session_consume(nghttp2_session *session, int32_t stream_id,
+                            size_t size) {
+  int rv;
+  nghttp2_stream *stream;
+
+  if (stream_id == 0) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (!(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE)) {
+    return NGHTTP2_ERR_INVALID_STATE;
+  }
+
+  rv = session_update_connection_consumed_size(session, size);
+
+  if (nghttp2_is_fatal(rv)) {
+    return rv;
+  }
+
+  stream = nghttp2_session_get_stream(session, stream_id);
+
+  if (stream) {
+    rv = session_update_stream_consumed_size(session, stream, size);
+
+    if (nghttp2_is_fatal(rv)) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+int nghttp2_session_set_next_stream_id(nghttp2_session *session,
+                                       int32_t next_stream_id) {
+  if (next_stream_id < 0 ||
+      session->next_stream_id > (uint32_t)next_stream_id) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  session->next_stream_id = next_stream_id;
+  return 0;
+}
+
+uint32_t nghttp2_session_get_next_stream_id(nghttp2_session *session) {
+  return session->next_stream_id;
+}
+
+int32_t nghttp2_session_get_last_proc_stream_id(nghttp2_session *session) {
+  return session->last_proc_stream_id;
+}
diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h
new file mode 100644 (file)
index 0000000..900bf46
--- /dev/null
@@ -0,0 +1,776 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_SESSION_H
+#define NGHTTP2_SESSION_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_pq.h"
+#include "nghttp2_map.h"
+#include "nghttp2_frame.h"
+#include "nghttp2_hd.h"
+#include "nghttp2_stream.h"
+#include "nghttp2_outbound_item.h"
+#include "nghttp2_int.h"
+#include "nghttp2_buf.h"
+#include "nghttp2_callbacks.h"
+#include "nghttp2_mem.h"
+
+/*
+ * Option flags.
+ */
+typedef enum {
+  NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE = 1 << 0,
+  NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE = 1 << 1,
+} nghttp2_optmask;
+
+typedef enum {
+  NGHTTP2_OB_POP_ITEM,
+  NGHTTP2_OB_SEND_DATA
+} nghttp2_outbound_state;
+
+typedef struct {
+  nghttp2_outbound_item *item;
+  nghttp2_bufs framebufs;
+  nghttp2_outbound_state state;
+} nghttp2_active_outbound_item;
+
+/* Buffer length for inbound raw byte stream used in
+   nghttp2_session_recv(). */
+#define NGHTTP2_INBOUND_BUFFER_LENGTH 16384
+
+/* Internal state when receiving incoming frame */
+typedef enum {
+  /* Receiving frame header */
+  NGHTTP2_IB_READ_CLIENT_PREFACE,
+  NGHTTP2_IB_READ_FIRST_SETTINGS,
+  NGHTTP2_IB_READ_HEAD,
+  NGHTTP2_IB_READ_NBYTE,
+  NGHTTP2_IB_READ_HEADER_BLOCK,
+  NGHTTP2_IB_IGN_HEADER_BLOCK,
+  NGHTTP2_IB_IGN_PAYLOAD,
+  NGHTTP2_IB_FRAME_SIZE_ERROR,
+  NGHTTP2_IB_READ_SETTINGS,
+  NGHTTP2_IB_READ_GOAWAY_DEBUG,
+  NGHTTP2_IB_EXPECT_CONTINUATION,
+  NGHTTP2_IB_IGN_CONTINUATION,
+  NGHTTP2_IB_READ_PAD_DATA,
+  NGHTTP2_IB_READ_DATA,
+  NGHTTP2_IB_IGN_DATA
+} nghttp2_inbound_state;
+
+#define NGHTTP2_INBOUND_NUM_IV 7
+
+typedef struct {
+  nghttp2_frame frame;
+  /* Storage for extension frame payload.  frame->ext.payload points
+     to this structure to avoid frequent memory allocation. */
+  nghttp2_ext_frame_payload ext_frame_payload;
+  /* The received SETTINGS entry. The protocol says that we only cares
+     about the defined settings ID. If unknown ID is received, it is
+     ignored.  We use last entry to hold minimum header table size if
+     same settings are multiple times. */
+  nghttp2_settings_entry iv[NGHTTP2_INBOUND_NUM_IV];
+  /* buffer pointers to small buffer, raw_sbuf */
+  nghttp2_buf sbuf;
+  /* buffer pointers to large buffer, raw_lbuf */
+  nghttp2_buf lbuf;
+  /* Large buffer, malloced on demand */
+  uint8_t *raw_lbuf;
+  /* The number of entry filled in |iv| */
+  size_t niv;
+  /* How many bytes we still need to receive for current frame */
+  size_t payloadleft;
+  /* padding length for the current frame */
+  size_t padlen;
+  nghttp2_inbound_state state;
+  /* Small buffer.  Currently the largest contiguous chunk to buffer
+     is frame header.  We buffer part of payload, but they are smaller
+     than frame header. */
+  uint8_t raw_sbuf[NGHTTP2_FRAME_HDLEN];
+} nghttp2_inbound_frame;
+
+typedef struct {
+  uint32_t header_table_size;
+  uint32_t enable_push;
+  uint32_t max_concurrent_streams;
+  uint32_t initial_window_size;
+  uint32_t max_frame_size;
+  uint32_t max_header_list_size;
+} nghttp2_settings_storage;
+
+typedef enum {
+  NGHTTP2_GOAWAY_NONE = 0,
+  /* Flag means that connection should be terminated after sending GOAWAY. */
+  NGHTTP2_GOAWAY_TERM_ON_SEND = 0x1,
+  /* Flag means GOAWAY to terminate session has been sent */
+  NGHTTP2_GOAWAY_TERM_SENT = 0x2,
+  /* Flag means GOAWAY was sent */
+  NGHTTP2_GOAWAY_SENT = 0x4,
+  /* Flag means GOAWAY was received */
+  NGHTTP2_GOAWAY_RECV = 0x8,
+} nghttp2_goaway_flag;
+
+struct nghttp2_session {
+  nghttp2_map /* <nghttp2_stream*> */ streams;
+  nghttp2_stream_roots roots;
+  /* Queue for outbound frames other than stream-creating HEADERS and
+     DATA */
+  nghttp2_pq /* <nghttp2_outbound_item*> */ ob_pq;
+  /* Queue for outbound stream-creating HEADERS frame */
+  nghttp2_pq /* <nghttp2_outbound_item*> */ ob_ss_pq;
+  /* QUeue for DATA frame */
+  nghttp2_pq /* <nghttp2_outbound_item*> */ ob_da_pq;
+  nghttp2_active_outbound_item aob;
+  nghttp2_inbound_frame iframe;
+  nghttp2_hd_deflater hd_deflater;
+  nghttp2_hd_inflater hd_inflater;
+  nghttp2_session_callbacks callbacks;
+  /* Memory allocator */
+  nghttp2_mem mem;
+  /* Sequence number of outbound frame to maintain the order of
+     enqueue if priority is equal. */
+  int64_t next_seq;
+  /* Reset count of nghttp2_outbound_item's weight.  We decrements
+     weight each time DATA is sent to simulate resource sharing.  We
+     use priority queue and larger weight has the precedence.  If
+     weight is reached to lowest weight, it resets to its initial
+     weight.  If this happens, other items which have the lower weight
+     currently but same initial weight cannot send DATA until item
+     having large weight is decreased.  To avoid this, we use this
+     cycle variable.  Initally, this is set to 1.  If weight gets
+     lowest weight, and if item's cycle == last_cycle, we increments
+     last_cycle and assigns it to item's cycle.  Otherwise, just
+     assign last_cycle.  In priority queue comparator, we first
+     compare items' cycle value.  Lower cycle value has the
+     precedence. */
+  uint64_t last_cycle;
+  void *user_data;
+  /* Points to the latest closed stream.  NULL if there is no closed
+     stream.  Only used when session is initialized as server. */
+  nghttp2_stream *closed_stream_head;
+  /* Points to the oldest closed stream.  NULL if there is no closed
+     stream.  Only used when session is initialized as server. */
+  nghttp2_stream *closed_stream_tail;
+  /* Points to the latest idle stream.  NULL if there is no idle
+     stream.  Only used when session is initialized as server .*/
+  nghttp2_stream *idle_stream_head;
+  /* Points to the oldest idle stream.  NULL if there is no idle
+     stream.  Only used when session is initialized as erver. */
+  nghttp2_stream *idle_stream_tail;
+  /* In-flight SETTINGS values. NULL does not necessarily mean there
+     is no in-flight SETTINGS. */
+  nghttp2_settings_entry *inflight_iv;
+  /* The number of entries in |inflight_iv|. -1 if there is no
+     in-flight SETTINGS. */
+  ssize_t inflight_niv;
+  /* The number of outgoing streams. This will be capped by
+     remote_settings.max_concurrent_streams. */
+  size_t num_outgoing_streams;
+  /* The number of incoming streams. This will be capped by
+     local_settings.max_concurrent_streams. */
+  size_t num_incoming_streams;
+  /* The number of closed streams still kept in |streams| hash.  The
+     closed streams can be accessed through single linked list
+     |closed_stream_head|.  The current implementation only keeps
+     incoming streams and session is initialized as server. */
+  size_t num_closed_streams;
+  /* The number of idle streams kept in |streams| hash.  The idle
+     streams can be accessed through doubly linked list
+     |idle_stream_head|.  The current implementation only keeps idle
+     streams if session is initialized as server. */
+  size_t num_idle_streams;
+  /* The number of bytes allocated for nvbuf */
+  size_t nvbuflen;
+  /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
+  uint32_t next_stream_id;
+  /* The largest stream ID received so far */
+  int32_t last_recv_stream_id;
+  /* The largest stream ID which has been processed in some way. This
+     value will be used as last-stream-id when sending GOAWAY
+     frame. */
+  int32_t last_proc_stream_id;
+  /* Counter of unique ID of PING. Wraps when it exceeds
+     NGHTTP2_MAX_UNIQUE_ID */
+  uint32_t next_unique_id;
+  /* This is the last-stream-ID we have sent in GOAWAY */
+  int32_t local_last_stream_id;
+  /* This is the value in GOAWAY frame received from remote endpoint. */
+  int32_t remote_last_stream_id;
+  /* Current sender window size. This value is computed against the
+     current initial window size of remote endpoint. */
+  int32_t remote_window_size;
+  /* Keep track of the number of bytes received without
+     WINDOW_UPDATE. This could be negative after submitting negative
+     value to WINDOW_UPDATE. */
+  int32_t recv_window_size;
+  /* The number of bytes consumed by the application and now is
+     subject to WINDOW_UPDATE.  This is only used when auto
+     WINDOW_UPDATE is turned off. */
+  int32_t consumed_size;
+  /* The amount of recv_window_size cut using submitting negative
+     value to WINDOW_UPDATE */
+  int32_t recv_reduction;
+  /* window size for local flow control. It is initially set to
+     NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE and could be
+     increased/decreased by submitting WINDOW_UPDATE. See
+     nghttp2_submit_window_update(). */
+  int32_t local_window_size;
+  /* Settings value received from the remote endpoint. We just use ID
+     as index. The index = 0 is unused. */
+  nghttp2_settings_storage remote_settings;
+  /* Settings value of the local endpoint. */
+  nghttp2_settings_storage local_settings;
+  /* Option flags. This is bitwise-OR of 0 or more of nghttp2_optmask. */
+  uint32_t opt_flags;
+  /* Unacked local SETTINGS_MAX_CONCURRENT_STREAMS value. We use this
+     to refuse the incoming stream if it exceeds this value. */
+  uint32_t pending_local_max_concurrent_stream;
+  /* Nonzero if the session is server side. */
+  uint8_t server;
+  /* Flags indicating GOAWAY is sent and/or recieved. The flags are
+     composed by bitwise OR-ing nghttp2_goaway_flag. */
+  uint8_t goaway_flags;
+};
+
+/* Struct used when updating initial window size of each active
+   stream. */
+typedef struct {
+  nghttp2_session *session;
+  int32_t new_window_size, old_window_size;
+} nghttp2_update_window_size_arg;
+
+typedef struct {
+  nghttp2_session *session;
+  /* linked list of streams to close */
+  nghttp2_stream *head;
+  int32_t last_stream_id;
+  /* nonzero if GOAWAY is sent to peer, which means we are going to
+     close incoming streams.  zero if GOAWAY is received from peer and
+     we are going to close outgoing streams. */
+  int incoming;
+} nghttp2_close_stream_on_goaway_arg;
+
+/* TODO stream timeout etc */
+
+/*
+ * Returns nonzero value if |stream_id| is initiated by local
+ * endpoint.
+ */
+int nghttp2_session_is_my_stream_id(nghttp2_session *session,
+                                    int32_t stream_id);
+
+/*
+ * Initializes |item|.  No memory allocation is done in this function.
+ * Don't call nghttp2_outbound_item_free() until frame member is
+ * initialized.
+ */
+void nghttp2_session_outbound_item_init(nghttp2_session *session,
+                                        nghttp2_outbound_item *item);
+
+/*
+ * Adds |item| to the outbound queue in |session|.  When this function
+ * succeeds, it takes ownership of |item|. So caller must not free it
+ * on success.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_STREAM_CLOSED
+ *     Stream already closed (DATA frame only)
+ */
+int nghttp2_session_add_item(nghttp2_session *session,
+                             nghttp2_outbound_item *item);
+
+/*
+ * Adds RST_STREAM frame for the stream |stream_id| with the error
+ * code |error_code|. This is a convenient function built on top of
+ * nghttp2_session_add_frame() to add RST_STREAM easily.
+ *
+ * This function simply returns 0 without adding RST_STREAM frame if
+ * given stream is in NGHTTP2_STREAM_CLOSING state, because multiple
+ * RST_STREAM for a stream is redundant.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id,
+                                   uint32_t error_code);
+
+/*
+ * Adds PING frame. This is a convenient functin built on top of
+ * nghttp2_session_add_frame() to add PING easily.
+ *
+ * If the |opaque_data| is not NULL, it must point to 8 bytes memory
+ * region of data. The data pointed by |opaque_data| is copied. It can
+ * be NULL. In this case, 8 bytes NULL is used.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,
+                             const uint8_t *opaque_data);
+
+/*
+ * Adds GOAWAY frame with the last-stream-ID |last_stream_id| and the
+ * error code |error_code|. This is a convenient function built on top
+ * of nghttp2_session_add_frame() to add GOAWAY easily.  The
+ * |aux_flags| are bitwise-OR of one or more of
+ * nghttp2_goaway_aux_flag.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     The |opaque_data_len| is too large.
+ */
+int nghttp2_session_add_goaway(nghttp2_session *session, int32_t last_stream_id,
+                               uint32_t error_code, const uint8_t *opaque_data,
+                               size_t opaque_data_len, uint8_t aux_flags);
+
+/*
+ * Adds WINDOW_UPDATE frame with stream ID |stream_id| and
+ * window-size-increment |window_size_increment|. This is a convenient
+ * function built on top of nghttp2_session_add_frame() to add
+ * WINDOW_UPDATE easily.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
+                                      int32_t stream_id,
+                                      int32_t window_size_increment);
+
+/*
+ * Adds SETTINGS frame.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
+                                 const nghttp2_settings_entry *iv, size_t niv);
+
+/*
+ * Creates new stream in |session| with stream ID |stream_id|,
+ * priority |pri_spec| and flags |flags|.  The |flags| is bitwise OR
+ * of nghttp2_stream_flag.  Since this function is called when initial
+ * HEADERS is sent or received, these flags are taken from it.  The
+ * state of stream is set to |initial_state|. The |stream_user_data|
+ * is a pointer to the arbitrary user supplied data to be associated
+ * to this stream.
+ *
+ * This function returns a pointer to created new stream object, or
+ * NULL.
+ */
+nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
+                                            int32_t stream_id, uint8_t flags,
+                                            nghttp2_priority_spec *pri_spec,
+                                            nghttp2_stream_state initial_state,
+                                            void *stream_user_data);
+
+/*
+ * Closes stream whose stream ID is |stream_id|. The reason of closure
+ * is indicated by the |error_code|. When closing the stream,
+ * on_stream_close_callback will be called.
+ *
+ * If the session is initialized as server and |stream| is incoming
+ * stream, stream is just marked closed and this function calls
+ * nghttp2_session_keep_closed_stream() with |stream|.  Otherwise,
+ * |stream| will be deleted from memory.
+ *
+ * This function returns 0 if it succeeds, or one the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     The specified stream does not exist.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The callback function failed.
+ */
+int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
+                                 uint32_t error_code);
+
+/*
+ * Deletes |stream| from memory.  After this function returns, stream
+ * cannot be accessed.
+ *
+ */
+void nghttp2_session_destroy_stream(nghttp2_session *session,
+                                    nghttp2_stream *stream);
+
+/*
+ * Tries to keep incoming closed stream |stream|.  Due to the
+ * limitation of maximum number of streams in memory, |stream| is not
+ * closed and just deleted from memory (see
+ * nghttp2_session_destroy_stream).
+ */
+void nghttp2_session_keep_closed_stream(nghttp2_session *session,
+                                        nghttp2_stream *stream);
+
+/*
+ * Appends |stream| to linked list |session->idle_stream_head|.  We
+ * apply fixed limit for list size.  To fit into that limit, one or
+ * more oldest streams are removed from list as necessary.
+ */
+void nghttp2_session_keep_idle_stream(nghttp2_session *session,
+                                      nghttp2_stream *stream);
+
+/*
+ * Detaches |stream| from idle streams linked list.
+ */
+void nghttp2_session_detach_idle_stream(nghttp2_session *session,
+                                        nghttp2_stream *stream);
+
+/*
+ * Deletes closed stream to ensure that number of incoming streams
+ * including active and closed is in the maximum number of allowed
+ * stream.  If |offset| is nonzero, it is decreased from the maximum
+ * number of allowed stream when comparing number of active and closed
+ * stream and the maximum number.
+ */
+void nghttp2_session_adjust_closed_stream(nghttp2_session *session,
+                                          ssize_t offset);
+
+/*
+ * Deletes idle stream to ensure that number of idle streams is in
+ * certain limit.
+ */
+void nghttp2_session_adjust_idle_stream(nghttp2_session *session);
+
+/*
+ * If further receptions and transmissions over the stream |stream_id|
+ * are disallowed, close the stream with error code NGHTTP2_NO_ERROR.
+ *
+ * This function returns 0 if it
+ * succeeds, or one of the following negative error codes:
+ *
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     The specified stream does not exist.
+ */
+int nghttp2_session_close_stream_if_shut_rdwr(nghttp2_session *session,
+                                              nghttp2_stream *stream);
+
+int nghttp2_session_end_request_headers_received(nghttp2_session *session,
+                                                 nghttp2_frame *frame,
+                                                 nghttp2_stream *stream);
+
+int nghttp2_session_end_response_headers_received(nghttp2_session *session,
+                                                  nghttp2_frame *frame,
+                                                  nghttp2_stream *stream);
+
+int nghttp2_session_end_headers_received(nghttp2_session *session,
+                                         nghttp2_frame *frame,
+                                         nghttp2_stream *stream);
+
+int nghttp2_session_on_request_headers_received(nghttp2_session *session,
+                                                nghttp2_frame *frame);
+
+int nghttp2_session_on_response_headers_received(nghttp2_session *session,
+                                                 nghttp2_frame *frame,
+                                                 nghttp2_stream *stream);
+
+int nghttp2_session_on_push_response_headers_received(nghttp2_session *session,
+                                                      nghttp2_frame *frame,
+                                                      nghttp2_stream *stream);
+
+/*
+ * Called when HEADERS is received, assuming |frame| is properly
+ * initialized.  This function does first validate received frame and
+ * then open stream and call callback functions.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_IGN_HEADER_BLOCK
+ *     Frame was rejected and header block must be decoded but
+ *     result must be ignored.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The read_callback failed
+ */
+int nghttp2_session_on_headers_received(nghttp2_session *session,
+                                        nghttp2_frame *frame,
+                                        nghttp2_stream *stream);
+
+/*
+ * Called when PRIORITY is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The read_callback failed
+ */
+int nghttp2_session_on_priority_received(nghttp2_session *session,
+                                         nghttp2_frame *frame);
+
+/*
+ * Called when RST_STREAM is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The read_callback failed
+ */
+int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
+                                           nghttp2_frame *frame);
+
+/*
+ * Called when SETTINGS is received, assuming |frame| is properly
+ * initialized. If |noack| is non-zero, SETTINGS with ACK will not be
+ * submitted. If |frame| has NGHTTP2_FLAG_ACK flag set, no SETTINGS
+ * with ACK will not be submitted regardless of |noack|.
+ *
+ * This function returns 0 if it succeeds, or one the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The read_callback failed
+ */
+int nghttp2_session_on_settings_received(nghttp2_session *session,
+                                         nghttp2_frame *frame, int noack);
+
+/*
+ * Called when PUSH_PROMISE is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_IGN_HEADER_BLOCK
+ *     Frame was rejected and header block must be decoded but
+ *     result must be ignored.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The read_callback failed
+ */
+int nghttp2_session_on_push_promise_received(nghttp2_session *session,
+                                             nghttp2_frame *frame);
+
+/*
+ * Called when PING is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *   The callback function failed.
+ */
+int nghttp2_session_on_ping_received(nghttp2_session *session,
+                                     nghttp2_frame *frame);
+
+/*
+ * Called when GOAWAY is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *   The callback function failed.
+ */
+int nghttp2_session_on_goaway_received(nghttp2_session *session,
+                                       nghttp2_frame *frame);
+
+/*
+ * Called when WINDOW_UPDATE is recieved, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *   The callback function failed.
+ */
+int nghttp2_session_on_window_update_received(nghttp2_session *session,
+                                              nghttp2_frame *frame);
+
+/*
+ * Called when DATA is received, assuming |frame| is properly
+ * initialized.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *   The callback function failed.
+ */
+int nghttp2_session_on_data_received(nghttp2_session *session,
+                                     nghttp2_frame *frame);
+
+/*
+ * Returns nghttp2_stream* object whose stream ID is |stream_id|.  It
+ * could be NULL if such stream does not exist.  This function returns
+ * NULL if stream is marked as closed.
+ */
+nghttp2_stream *nghttp2_session_get_stream(nghttp2_session *session,
+                                           int32_t stream_id);
+
+/*
+ * This function behaves like nghttp2_session_get_stream(), but it
+ * returns stream object even if it is marked as closed or in
+ * NGHTTP2_STREAM_IDLE state.
+ */
+nghttp2_stream *nghttp2_session_get_stream_raw(nghttp2_session *session,
+                                               int32_t stream_id);
+
+/*
+ * Packs DATA frame |frame| in wire frame format and stores it in
+ * |bufs|.  Payload will be read using |aux_data->data_prd|.  The
+ * length of payload is at most |datamax| bytes.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_DEFERRED
+ *     The DATA frame is postponed.
+ * NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
+ *     The read_callback failed (stream error).
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_CALLBACK_FAILURE
+ *     The read_callback failed (session error).
+ */
+int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
+                              size_t datamax, nghttp2_frame *frame,
+                              nghttp2_data_aux_data *aux_data);
+
+/*
+ * Returns top of outbound frame queue. This function returns NULL if
+ * queue is empty.
+ */
+nghttp2_outbound_item *nghttp2_session_get_ob_pq_top(nghttp2_session *session);
+
+/*
+ * Pops and returns next item to send. If there is no such item,
+ * returns NULL.  This function takes into account max concurrent
+ * streams. That means if session->ob_pq is empty but
+ * session->ob_ss_pq has item and max concurrent streams is reached,
+ * then this function returns NULL.
+ */
+nghttp2_outbound_item *
+nghttp2_session_pop_next_ob_item(nghttp2_session *session);
+
+/*
+ * Returns next item to send. If there is no such item, this function
+ * returns NULL.  This function takes into account max concurrent
+ * streams. That means if session->ob_pq is empty but
+ * session->ob_ss_pq has item and max concurrent streams is reached,
+ * then this function returns NULL.
+ */
+nghttp2_outbound_item *
+nghttp2_session_get_next_ob_item(nghttp2_session *session);
+
+/*
+ * Updates local settings with the |iv|. The number of elements in the
+ * array pointed by the |iv| is given by the |niv|.  This function
+ * assumes that the all settings_id member in |iv| are in range 1 to
+ * NGHTTP2_SETTINGS_MAX, inclusive.
+ *
+ * While updating individual stream's local window size, if the window
+ * size becomes strictly larger than NGHTTP2_MAX_WINDOW_SIZE,
+ * RST_STREAM is issued against such a stream.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_session_update_local_settings(nghttp2_session *session,
+                                          nghttp2_settings_entry *iv,
+                                          size_t niv);
+
+/*
+ * Re-prioritize |stream|. The new priority specification is
+ * |pri_spec|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_session_reprioritize_stream(nghttp2_session *session,
+                                        nghttp2_stream *stream,
+                                        const nghttp2_priority_spec *pri_spec);
+
+/*
+ * Terminates current |session| with the |error_code|.  The |reason|
+ * is NULL-terminated debug string.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ * NGHTTP2_ERR_INVALID_ARGUMENT
+ *     The |reason| is too long.
+ */
+int nghttp2_session_terminate_session_with_reason(nghttp2_session *session,
+                                                  uint32_t error_code,
+                                                  const char *reason);
+
+#endif /* NGHTTP2_SESSION_H */
diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c
new file mode 100644 (file)
index 0000000..8d5fec6
--- /dev/null
@@ -0,0 +1,1059 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_stream.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "nghttp2_session.h"
+#include "nghttp2_helper.h"
+
+void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
+                         uint8_t flags, nghttp2_stream_state initial_state,
+                         int32_t weight, nghttp2_stream_roots *roots,
+                         int32_t remote_initial_window_size,
+                         int32_t local_initial_window_size,
+                         void *stream_user_data) {
+  nghttp2_map_entry_init(&stream->map_entry, stream_id);
+  stream->stream_id = stream_id;
+  stream->flags = flags;
+  stream->state = initial_state;
+  stream->shut_flags = NGHTTP2_SHUT_NONE;
+  stream->stream_user_data = stream_user_data;
+  stream->item = NULL;
+  stream->remote_window_size = remote_initial_window_size;
+  stream->local_window_size = local_initial_window_size;
+  stream->recv_window_size = 0;
+  stream->consumed_size = 0;
+  stream->recv_reduction = 0;
+
+  stream->dep_prev = NULL;
+  stream->dep_next = NULL;
+  stream->sib_prev = NULL;
+  stream->sib_next = NULL;
+
+  stream->closed_prev = NULL;
+  stream->closed_next = NULL;
+
+  stream->dpri = NGHTTP2_STREAM_DPRI_NO_ITEM;
+  stream->num_substreams = 1;
+  stream->weight = weight;
+  stream->effective_weight = stream->weight;
+  stream->sum_dep_weight = 0;
+  stream->sum_norest_weight = 0;
+  stream->sum_top_weight = 0;
+
+  stream->roots = roots;
+  stream->root_prev = NULL;
+  stream->root_next = NULL;
+}
+
+void nghttp2_stream_free(nghttp2_stream *stream _U_) {
+  /* We don't free stream->item.  If it is assigned to aob, then
+     active_outbound_item_reset() will delete it.  If it is queued,
+     then it is deleted when pq is deleted in nghttp2_session_del().
+     Otherwise, nghttp2_session_del() will delete it. */
+}
+
+void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag) {
+  stream->shut_flags |= flag;
+}
+
+static int stream_push_item(nghttp2_stream *stream, nghttp2_session *session) {
+  int rv;
+  nghttp2_outbound_item *item;
+
+  assert(stream->item);
+  assert(stream->item->queued == 0);
+
+  item = stream->item;
+
+  /* If item is now sent, don't push it to the queue.  Otherwise, we
+     may push same item twice. */
+  if (session->aob.item == item) {
+    return 0;
+  }
+
+  if (item->weight > stream->effective_weight) {
+    item->weight = stream->effective_weight;
+  }
+
+  item->cycle = session->last_cycle;
+
+  switch (item->frame.hd.type) {
+  case NGHTTP2_DATA:
+    rv = nghttp2_pq_push(&session->ob_da_pq, item);
+    break;
+  case NGHTTP2_HEADERS:
+    if (stream->state == NGHTTP2_STREAM_RESERVED) {
+      rv = nghttp2_pq_push(&session->ob_ss_pq, item);
+    } else {
+      rv = nghttp2_pq_push(&session->ob_pq, item);
+    }
+    break;
+  default:
+    /* should not reach here */
+    assert(0);
+  }
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  item->queued = 1;
+
+  return 0;
+}
+
+static nghttp2_stream *stream_first_sib(nghttp2_stream *stream) {
+  for (; stream->sib_prev; stream = stream->sib_prev)
+    ;
+
+  return stream;
+}
+
+static nghttp2_stream *stream_last_sib(nghttp2_stream *stream) {
+  for (; stream->sib_next; stream = stream->sib_next)
+    ;
+
+  return stream;
+}
+
+static nghttp2_stream *stream_update_dep_length(nghttp2_stream *stream,
+                                                ssize_t delta) {
+  stream->num_substreams += delta;
+
+  stream = stream_first_sib(stream);
+
+  if (stream->dep_prev) {
+    return stream_update_dep_length(stream->dep_prev, delta);
+  }
+
+  return stream;
+}
+
+int32_t nghttp2_stream_dep_distributed_weight(nghttp2_stream *stream,
+                                              int32_t weight) {
+  weight = stream->weight * weight / stream->sum_dep_weight;
+
+  return nghttp2_max(1, weight);
+}
+
+int32_t nghttp2_stream_dep_distributed_effective_weight(nghttp2_stream *stream,
+                                                        int32_t weight) {
+  if (stream->sum_norest_weight == 0) {
+    return stream->effective_weight;
+  }
+
+  weight = stream->effective_weight * weight / stream->sum_norest_weight;
+
+  return nghttp2_max(1, weight);
+}
+
+static int32_t
+stream_dep_distributed_top_effective_weight(nghttp2_stream *stream,
+                                            int32_t weight) {
+  if (stream->sum_top_weight == 0) {
+    return stream->effective_weight;
+  }
+
+  weight = stream->effective_weight * weight / stream->sum_top_weight;
+
+  return nghttp2_max(1, weight);
+}
+
+static void stream_update_dep_set_rest(nghttp2_stream *stream);
+
+/* Updates effective_weight of descendant streams in subtree of
+   |stream|.  We assume that stream->effective_weight is already set
+   right. */
+static void stream_update_dep_effective_weight(nghttp2_stream *stream) {
+  nghttp2_stream *si;
+
+  DEBUGF(fprintf(stderr, "stream: update_dep_effective_weight "
+                         "stream(%p)=%d, weight=%d, sum_norest_weight=%d, "
+                         "sum_top_weight=%d\n",
+                 stream, stream->stream_id, stream->weight,
+                 stream->sum_norest_weight, stream->sum_top_weight));
+
+  /* stream->sum_norest_weight == 0 means there is no
+     NGHTTP2_STREAM_DPRI_TOP under stream */
+  if (stream->dpri != NGHTTP2_STREAM_DPRI_NO_ITEM ||
+      stream->sum_norest_weight == 0) {
+    return;
+  }
+
+  /* If there is no direct descendant whose dpri is
+     NGHTTP2_STREAM_DPRI_TOP, indirect descendants have the chance to
+     send data, so recursively set weight for descendants. */
+  if (stream->sum_top_weight == 0) {
+    for (si = stream->dep_next; si; si = si->sib_next) {
+      if (si->dpri != NGHTTP2_STREAM_DPRI_REST) {
+        si->effective_weight =
+            nghttp2_stream_dep_distributed_effective_weight(stream, si->weight);
+      }
+
+      stream_update_dep_effective_weight(si);
+    }
+    return;
+  }
+
+  /* If there is at least one direct descendant whose dpri is
+     NGHTTP2_STREAM_DPRI_TOP, we won't give a chance to indirect
+     descendants, since closed or blocked stream's weight is
+     distributed among its siblings */
+  for (si = stream->dep_next; si; si = si->sib_next) {
+    if (si->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+      si->effective_weight =
+          stream_dep_distributed_top_effective_weight(stream, si->weight);
+
+      DEBUGF(fprintf(stderr, "stream: stream=%d top eweight=%d\n",
+                     si->stream_id, si->effective_weight));
+
+      continue;
+    }
+
+    if (si->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM) {
+      DEBUGF(fprintf(stderr, "stream: stream=%d no_item, ignored\n",
+                     si->stream_id));
+
+      /* Since we marked NGHTTP2_STREAM_DPRI_TOP under si, we make
+         them NGHTTP2_STREAM_DPRI_REST again. */
+      stream_update_dep_set_rest(si->dep_next);
+    } else {
+      DEBUGF(
+          fprintf(stderr, "stream: stream=%d rest, ignored\n", si->stream_id));
+    }
+  }
+}
+
+static void stream_update_dep_set_rest(nghttp2_stream *stream) {
+  if (stream == NULL) {
+    return;
+  }
+
+  DEBUGF(fprintf(stderr, "stream: stream=%d is rest\n", stream->stream_id));
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
+    return;
+  }
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+    stream->dpri = NGHTTP2_STREAM_DPRI_REST;
+
+    stream_update_dep_set_rest(stream->sib_next);
+
+    return;
+  }
+
+  stream_update_dep_set_rest(stream->sib_next);
+  stream_update_dep_set_rest(stream->dep_next);
+}
+
+/*
+ * Performs dfs starting |stream|, search stream which can become
+ * NGHTTP2_STREAM_DPRI_TOP and set its dpri.
+ */
+static void stream_update_dep_set_top(nghttp2_stream *stream) {
+  nghttp2_stream *si;
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+    return;
+  }
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
+    DEBUGF(
+        fprintf(stderr, "stream: stream=%d item is top\n", stream->stream_id));
+
+    stream->dpri = NGHTTP2_STREAM_DPRI_TOP;
+
+    return;
+  }
+
+  for (si = stream->dep_next; si; si = si->sib_next) {
+    stream_update_dep_set_top(si);
+  }
+}
+
+/*
+ * Performs dfs starting |stream|, and dueue stream whose dpri is
+ * NGHTTP2_STREAM_DPRI_TOP and has not been queued yet.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory.
+ */
+static int stream_update_dep_queue_top(nghttp2_stream *stream,
+                                       nghttp2_session *session) {
+  int rv;
+  nghttp2_stream *si;
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
+    return 0;
+  }
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+    if (!stream->item->queued) {
+      DEBUGF(fprintf(stderr, "stream: stream=%d enqueue\n", stream->stream_id));
+      rv = stream_push_item(stream, session);
+
+      if (rv != 0) {
+        return rv;
+      }
+    }
+
+    return 0;
+  }
+
+  for (si = stream->dep_next; si; si = si->sib_next) {
+    rv = stream_update_dep_queue_top(si, session);
+
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  return 0;
+}
+
+/*
+ * Updates stream->sum_norest_weight and stream->sum_top_weight
+ * recursively.  We have to gather effective sum of weight of
+ * descendants.  If stream->dpri == NGHTTP2_STREAM_DPRI_NO_ITEM, we
+ * have to go deeper and check that any of its descendants has dpri
+ * value of NGHTTP2_STREAM_DPRI_TOP.  If so, we have to add weight of
+ * its direct descendants to stream->sum_norest_weight.  To make this
+ * work, this function returns 1 if any of its descendants has dpri
+ * value of NGHTTP2_STREAM_DPRI_TOP, otherwise 0.
+ *
+ * Calculating stream->sum_top-weight is very simple compared to
+ * stream->sum_norest_weight.  It just adds up the weight of direct
+ * descendants whose dpri is NGHTTP2_STREAM_DPRI_TOP.
+ */
+static int stream_update_dep_sum_norest_weight(nghttp2_stream *stream) {
+  nghttp2_stream *si;
+  int rv;
+
+  stream->sum_norest_weight = 0;
+  stream->sum_top_weight = 0;
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+    return 1;
+  }
+
+  if (stream->dpri == NGHTTP2_STREAM_DPRI_REST) {
+    return 0;
+  }
+
+  rv = 0;
+
+  for (si = stream->dep_next; si; si = si->sib_next) {
+
+    if (stream_update_dep_sum_norest_weight(si)) {
+      rv = 1;
+      stream->sum_norest_weight += si->weight;
+    }
+
+    if (si->dpri == NGHTTP2_STREAM_DPRI_TOP) {
+      stream->sum_top_weight += si->weight;
+    }
+  }
+
+  return rv;
+}
+
+static int stream_update_dep_on_attach_item(nghttp2_stream *stream,
+                                            nghttp2_session *session) {
+  nghttp2_stream *root_stream;
+
+  stream->dpri = NGHTTP2_STREAM_DPRI_REST;
+
+  stream_update_dep_set_rest(stream->dep_next);
+
+  root_stream = nghttp2_stream_get_dep_root(stream);
+
+  DEBUGF(fprintf(stderr, "root=%p, stream=%p\n", root_stream, stream));
+
+  stream_update_dep_set_top(root_stream);
+
+  stream_update_dep_sum_norest_weight(root_stream);
+  stream_update_dep_effective_weight(root_stream);
+
+  return stream_update_dep_queue_top(root_stream, session);
+}
+
+static int stream_update_dep_on_detach_item(nghttp2_stream *stream,
+                                            nghttp2_session *session) {
+  nghttp2_stream *root_stream;
+
+  stream->dpri = NGHTTP2_STREAM_DPRI_NO_ITEM;
+
+  root_stream = nghttp2_stream_get_dep_root(stream);
+
+  stream_update_dep_set_top(root_stream);
+
+  stream_update_dep_sum_norest_weight(root_stream);
+  stream_update_dep_effective_weight(root_stream);
+
+  return stream_update_dep_queue_top(root_stream, session);
+}
+
+int nghttp2_stream_attach_item(nghttp2_stream *stream,
+                               nghttp2_outbound_item *item,
+                               nghttp2_session *session) {
+  assert((stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) == 0);
+  assert(stream->item == NULL);
+
+  DEBUGF(fprintf(stderr, "stream: stream=%d attach item=%p\n",
+                 stream->stream_id, item));
+
+  stream->item = item;
+
+  return stream_update_dep_on_attach_item(stream, session);
+}
+
+int nghttp2_stream_detach_item(nghttp2_stream *stream,
+                               nghttp2_session *session) {
+  DEBUGF(fprintf(stderr, "stream: stream=%d detach item=%p\n",
+                 stream->stream_id, stream->item));
+
+  stream->item = NULL;
+  stream->flags &= ~NGHTTP2_STREAM_FLAG_DEFERRED_ALL;
+
+  return stream_update_dep_on_detach_item(stream, session);
+}
+
+int nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags,
+                              nghttp2_session *session) {
+  assert(stream->item);
+
+  DEBUGF(fprintf(stderr, "stream: stream=%d defer item=%p cause=%02x\n",
+                 stream->stream_id, stream->item, flags));
+
+  stream->flags |= flags;
+
+  return stream_update_dep_on_detach_item(stream, session);
+}
+
+int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags,
+                                        nghttp2_session *session) {
+  assert(stream->item);
+
+  DEBUGF(fprintf(stderr, "stream: stream=%d resume item=%p flags=%02x\n",
+                 stream->stream_id, stream->item, flags));
+
+  stream->flags &= ~flags;
+
+  if (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL) {
+    return 0;
+  }
+
+  return stream_update_dep_on_attach_item(stream, session);
+}
+
+int nghttp2_stream_check_deferred_item(nghttp2_stream *stream) {
+  return stream->item && (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL);
+}
+
+int nghttp2_stream_check_deferred_by_flow_control(nghttp2_stream *stream) {
+  return stream->item &&
+         (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
+}
+
+static int update_initial_window_size(int32_t *window_size_ptr,
+                                      int32_t new_initial_window_size,
+                                      int32_t old_initial_window_size) {
+  int64_t new_window_size = (int64_t)(*window_size_ptr) +
+                            new_initial_window_size - old_initial_window_size;
+  if (INT32_MIN > new_window_size ||
+      new_window_size > NGHTTP2_MAX_WINDOW_SIZE) {
+    return -1;
+  }
+  *window_size_ptr = (int32_t)new_window_size;
+  return 0;
+}
+
+int nghttp2_stream_update_remote_initial_window_size(
+    nghttp2_stream *stream, int32_t new_initial_window_size,
+    int32_t old_initial_window_size) {
+  return update_initial_window_size(&stream->remote_window_size,
+                                    new_initial_window_size,
+                                    old_initial_window_size);
+}
+
+int nghttp2_stream_update_local_initial_window_size(
+    nghttp2_stream *stream, int32_t new_initial_window_size,
+    int32_t old_initial_window_size) {
+  return update_initial_window_size(&stream->local_window_size,
+                                    new_initial_window_size,
+                                    old_initial_window_size);
+}
+
+void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream) {
+  stream->state = NGHTTP2_STREAM_OPENED;
+}
+
+nghttp2_stream *nghttp2_stream_get_dep_root(nghttp2_stream *stream) {
+  for (;;) {
+    if (stream->sib_prev) {
+      stream = stream->sib_prev;
+
+      continue;
+    }
+
+    if (stream->dep_prev) {
+      stream = stream->dep_prev;
+
+      continue;
+    }
+
+    break;
+  }
+
+  return stream;
+}
+
+int nghttp2_stream_dep_subtree_find(nghttp2_stream *stream,
+                                    nghttp2_stream *target) {
+  if (stream == NULL) {
+    return 0;
+  }
+
+  if (stream == target) {
+    return 1;
+  }
+
+  if (nghttp2_stream_dep_subtree_find(stream->sib_next, target)) {
+    return 1;
+  }
+
+  return nghttp2_stream_dep_subtree_find(stream->dep_next, target);
+}
+
+void nghttp2_stream_dep_insert(nghttp2_stream *dep_stream,
+                               nghttp2_stream *stream) {
+  nghttp2_stream *si;
+  nghttp2_stream *root_stream;
+
+  assert(stream->item == NULL);
+
+  DEBUGF(fprintf(stderr,
+                 "stream: dep_insert dep_stream(%p)=%d, stream(%p)=%d\n",
+                 dep_stream, dep_stream->stream_id, stream, stream->stream_id));
+
+  stream->sum_dep_weight = dep_stream->sum_dep_weight;
+  dep_stream->sum_dep_weight = stream->weight;
+
+  if (dep_stream->dep_next) {
+    for (si = dep_stream->dep_next; si; si = si->sib_next) {
+      stream->num_substreams += si->num_substreams;
+    }
+
+    stream->dep_next = dep_stream->dep_next;
+    stream->dep_next->dep_prev = stream;
+  }
+
+  dep_stream->dep_next = stream;
+  stream->dep_prev = dep_stream;
+
+  root_stream = stream_update_dep_length(dep_stream, 1);
+
+  stream_update_dep_sum_norest_weight(root_stream);
+  stream_update_dep_effective_weight(root_stream);
+
+  ++stream->roots->num_streams;
+}
+
+static void link_dep(nghttp2_stream *dep_stream, nghttp2_stream *stream) {
+  dep_stream->dep_next = stream;
+  stream->dep_prev = dep_stream;
+}
+
+static void link_sib(nghttp2_stream *prev_stream, nghttp2_stream *stream) {
+  prev_stream->sib_next = stream;
+  stream->sib_prev = prev_stream;
+}
+
+static void insert_link_dep(nghttp2_stream *dep_stream,
+                            nghttp2_stream *stream) {
+  nghttp2_stream *sib_next;
+
+  assert(stream->sib_prev == NULL);
+
+  sib_next = dep_stream->dep_next;
+
+  link_sib(stream, sib_next);
+
+  sib_next->dep_prev = NULL;
+
+  link_dep(dep_stream, stream);
+}
+
+static void unlink_sib(nghttp2_stream *stream) {
+  nghttp2_stream *prev, *next, *dep_next;
+
+  prev = stream->sib_prev;
+  dep_next = stream->dep_next;
+
+  assert(prev);
+
+  if (dep_next) {
+    /*
+     *  prev--stream(--sib_next--...)
+     *         |
+     *        dep_next
+     */
+    dep_next->dep_prev = NULL;
+
+    link_sib(prev, dep_next);
+
+    if (stream->sib_next) {
+      link_sib(stream_last_sib(dep_next), stream->sib_next);
+    }
+  } else {
+    /*
+     *  prev--stream(--sib_next--...)
+     */
+    next = stream->sib_next;
+
+    prev->sib_next = next;
+
+    if (next) {
+      next->sib_prev = prev;
+    }
+  }
+}
+
+static void unlink_dep(nghttp2_stream *stream) {
+  nghttp2_stream *prev, *next, *dep_next;
+
+  prev = stream->dep_prev;
+  dep_next = stream->dep_next;
+
+  assert(prev);
+
+  if (dep_next) {
+    /*
+     * prev
+     *   |
+     * stream(--sib_next--...)
+     *   |
+     * dep_next
+     */
+    link_dep(prev, dep_next);
+
+    if (stream->sib_next) {
+      link_sib(stream_last_sib(dep_next), stream->sib_next);
+    }
+  } else if (stream->sib_next) {
+    /*
+     * prev
+     *   |
+     * stream--sib_next
+     */
+    next = stream->sib_next;
+
+    next->sib_prev = NULL;
+
+    link_dep(prev, next);
+  } else {
+    prev->dep_next = NULL;
+  }
+}
+
+void nghttp2_stream_dep_add(nghttp2_stream *dep_stream,
+                            nghttp2_stream *stream) {
+  nghttp2_stream *root_stream;
+
+  assert(stream->item == NULL);
+
+  DEBUGF(fprintf(stderr, "stream: dep_add dep_stream(%p)=%d, stream(%p)=%d\n",
+                 dep_stream, dep_stream->stream_id, stream, stream->stream_id));
+
+  root_stream = stream_update_dep_length(dep_stream, 1);
+
+  dep_stream->sum_dep_weight += stream->weight;
+
+  if (dep_stream->dep_next == NULL) {
+    link_dep(dep_stream, stream);
+  } else {
+    insert_link_dep(dep_stream, stream);
+  }
+
+  stream_update_dep_sum_norest_weight(root_stream);
+  stream_update_dep_effective_weight(root_stream);
+
+  ++stream->roots->num_streams;
+}
+
+void nghttp2_stream_dep_remove(nghttp2_stream *stream) {
+  nghttp2_stream *prev, *next, *dep_prev, *si, *root_stream;
+  int32_t sum_dep_weight_delta;
+
+  root_stream = NULL;
+
+  DEBUGF(fprintf(stderr, "stream: dep_remove stream(%p)=%d\n", stream,
+                 stream->stream_id));
+
+  /* Distribute weight of |stream| to direct descendants */
+  sum_dep_weight_delta = -stream->weight;
+
+  for (si = stream->dep_next; si; si = si->sib_next) {
+    si->weight = nghttp2_stream_dep_distributed_weight(stream, si->weight);
+
+    sum_dep_weight_delta += si->weight;
+  }
+
+  prev = stream_first_sib(stream);
+
+  dep_prev = prev->dep_prev;
+
+  if (dep_prev) {
+    root_stream = stream_update_dep_length(dep_prev, -1);
+
+    dep_prev->sum_dep_weight += sum_dep_weight_delta;
+  }
+
+  if (stream->sib_prev) {
+    unlink_sib(stream);
+  } else if (stream->dep_prev) {
+    unlink_dep(stream);
+  } else {
+    nghttp2_stream_roots_remove(stream->roots, stream);
+
+    /* stream is a root of tree.  Removing stream makes its
+       descendants a root of its own subtree. */
+
+    for (si = stream->dep_next; si;) {
+      next = si->sib_next;
+
+      si->dep_prev = NULL;
+      si->sib_prev = NULL;
+      si->sib_next = NULL;
+
+      /* We already distributed weight of |stream| to this. */
+      si->effective_weight = si->weight;
+
+      nghttp2_stream_roots_add(si->roots, si);
+
+      si = next;
+    }
+  }
+
+  if (root_stream) {
+    stream_update_dep_sum_norest_weight(root_stream);
+    stream_update_dep_effective_weight(root_stream);
+  }
+
+  stream->num_substreams = 1;
+  stream->sum_dep_weight = 0;
+
+  stream->dep_prev = NULL;
+  stream->dep_next = NULL;
+  stream->sib_prev = NULL;
+  stream->sib_next = NULL;
+
+  --stream->roots->num_streams;
+}
+
+int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream,
+                                      nghttp2_stream *stream,
+                                      nghttp2_session *session) {
+  nghttp2_stream *last_sib;
+  nghttp2_stream *dep_next;
+  nghttp2_stream *root_stream;
+  size_t delta_substreams;
+
+  DEBUGF(fprintf(stderr, "stream: dep_insert_subtree dep_stream(%p)=%d "
+                         "stream(%p)=%d\n",
+                 dep_stream, dep_stream->stream_id, stream, stream->stream_id));
+
+  delta_substreams = stream->num_substreams;
+
+  stream_update_dep_set_rest(stream);
+
+  if (dep_stream->dep_next) {
+    /* dep_stream->num_substreams includes dep_stream itself */
+    stream->num_substreams += dep_stream->num_substreams - 1;
+
+    stream->sum_dep_weight += dep_stream->sum_dep_weight;
+    dep_stream->sum_dep_weight = stream->weight;
+
+    dep_next = dep_stream->dep_next;
+
+    stream_update_dep_set_rest(dep_next);
+
+    link_dep(dep_stream, stream);
+
+    if (stream->dep_next) {
+      last_sib = stream_last_sib(stream->dep_next);
+
+      link_sib(last_sib, dep_next);
+
+      dep_next->dep_prev = NULL;
+    } else {
+      link_dep(stream, dep_next);
+    }
+  } else {
+    link_dep(dep_stream, stream);
+
+    assert(dep_stream->sum_dep_weight == 0);
+    dep_stream->sum_dep_weight = stream->weight;
+  }
+
+  root_stream = stream_update_dep_length(dep_stream, delta_substreams);
+
+  stream_update_dep_set_top(root_stream);
+
+  stream_update_dep_sum_norest_weight(root_stream);
+  stream_update_dep_effective_weight(root_stream);
+
+  return stream_update_dep_queue_top(root_stream, session);
+}
+
+int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream,
+                                   nghttp2_stream *stream,
+                                   nghttp2_session *session) {
+  nghttp2_stream *root_stream;
+
+  DEBUGF(fprintf(stderr, "stream: dep_add_subtree dep_stream(%p)=%d "
+                         "stream(%p)=%d\n",
+                 dep_stream, dep_stream->stream_id, stream, stream->stream_id));
+
+  stream_update_dep_set_rest(stream);
+
+  if (dep_stream->dep_next) {
+    dep_stream->sum_dep_weight += stream->weight;
+
+    insert_link_dep(dep_stream, stream);
+  } else {
+    link_dep(dep_stream, stream);
+
+    assert(dep_stream->sum_dep_weight == 0);
+    dep_stream->sum_dep_weight = stream->weight;
+  }
+
+  root_stream = stream_update_dep_length(dep_stream, stream->num_substreams);
+
+  stream_update_dep_set_top(root_stream);
+
+  stream_update_dep_sum_norest_weight(root_stream);
+  stream_update_dep_effective_weight(root_stream);
+
+  return stream_update_dep_queue_top(root_stream, session);
+}
+
+void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream) {
+  nghttp2_stream *prev, *next, *dep_prev, *root_stream;
+
+  DEBUGF(fprintf(stderr, "stream: dep_remove_subtree stream(%p)=%d\n", stream,
+                 stream->stream_id));
+
+  if (stream->sib_prev) {
+    prev = stream->sib_prev;
+
+    prev->sib_next = stream->sib_next;
+    if (prev->sib_next) {
+      prev->sib_next->sib_prev = prev;
+    }
+
+    prev = stream_first_sib(prev);
+
+    dep_prev = prev->dep_prev;
+
+  } else if (stream->dep_prev) {
+    dep_prev = stream->dep_prev;
+    next = stream->sib_next;
+
+    dep_prev->dep_next = next;
+
+    if (next) {
+      next->dep_prev = dep_prev;
+
+      next->sib_prev = NULL;
+    }
+
+  } else {
+    nghttp2_stream_roots_remove(stream->roots, stream);
+
+    dep_prev = NULL;
+  }
+
+  if (dep_prev) {
+    dep_prev->sum_dep_weight -= stream->weight;
+
+    root_stream = stream_update_dep_length(dep_prev, -stream->num_substreams);
+
+    stream_update_dep_sum_norest_weight(root_stream);
+    stream_update_dep_effective_weight(root_stream);
+  }
+
+  stream->sib_prev = NULL;
+  stream->sib_next = NULL;
+  stream->dep_prev = NULL;
+}
+
+int nghttp2_stream_dep_make_root(nghttp2_stream *stream,
+                                 nghttp2_session *session) {
+  DEBUGF(fprintf(stderr, "stream: dep_make_root stream(%p)=%d\n", stream,
+                 stream->stream_id));
+
+  nghttp2_stream_roots_add(stream->roots, stream);
+
+  stream_update_dep_set_rest(stream);
+
+  stream->effective_weight = stream->weight;
+
+  stream_update_dep_set_top(stream);
+
+  stream_update_dep_sum_norest_weight(stream);
+  stream_update_dep_effective_weight(stream);
+
+  return stream_update_dep_queue_top(stream, session);
+}
+
+int
+nghttp2_stream_dep_all_your_stream_are_belong_to_us(nghttp2_stream *stream,
+                                                    nghttp2_session *session) {
+  nghttp2_stream *first, *si;
+
+  DEBUGF(fprintf(stderr, "stream: ALL YOUR STREAM ARE BELONG TO US "
+                         "stream(%p)=%d\n",
+                 stream, stream->stream_id));
+
+  first = stream->roots->head;
+
+  /* stream must not be include in stream->roots->head list */
+  assert(first != stream);
+
+  if (first) {
+    nghttp2_stream *prev;
+
+    prev = first;
+
+    DEBUGF(fprintf(stderr, "stream: root stream(%p)=%d\n", first,
+                   first->stream_id));
+
+    stream->sum_dep_weight += first->weight;
+    stream->num_substreams += first->num_substreams;
+
+    for (si = first->root_next; si; si = si->root_next) {
+
+      assert(si != stream);
+
+      DEBUGF(
+          fprintf(stderr, "stream: root stream(%p)=%d\n", si, si->stream_id));
+
+      stream->sum_dep_weight += si->weight;
+      stream->num_substreams += si->num_substreams;
+
+      link_sib(prev, si);
+
+      prev = si;
+    }
+
+    if (stream->dep_next) {
+      nghttp2_stream *sib_next;
+
+      sib_next = stream->dep_next;
+
+      sib_next->dep_prev = NULL;
+
+      link_sib(first, sib_next);
+      link_dep(stream, prev);
+    } else {
+      link_dep(stream, first);
+    }
+  }
+
+  nghttp2_stream_roots_remove_all(stream->roots);
+
+  return nghttp2_stream_dep_make_root(stream, session);
+}
+
+int nghttp2_stream_in_dep_tree(nghttp2_stream *stream) {
+  return stream->dep_prev || stream->dep_next || stream->sib_prev ||
+         stream->sib_next || stream->root_next || stream->root_prev ||
+         stream->roots->head == stream;
+}
+
+void nghttp2_stream_roots_init(nghttp2_stream_roots *roots) {
+  roots->head = NULL;
+  roots->num_streams = 0;
+}
+
+void nghttp2_stream_roots_free(nghttp2_stream_roots *roots _U_) {}
+
+void nghttp2_stream_roots_add(nghttp2_stream_roots *roots,
+                              nghttp2_stream *stream) {
+  if (roots->head) {
+    stream->root_next = roots->head;
+    roots->head->root_prev = stream;
+  }
+
+  roots->head = stream;
+}
+
+void nghttp2_stream_roots_remove(nghttp2_stream_roots *roots,
+                                 nghttp2_stream *stream) {
+  nghttp2_stream *root_prev, *root_next;
+
+  root_prev = stream->root_prev;
+  root_next = stream->root_next;
+
+  if (root_prev) {
+    root_prev->root_next = root_next;
+
+    if (root_next) {
+      root_next->root_prev = root_prev;
+    }
+  } else {
+    if (root_next) {
+      root_next->root_prev = NULL;
+    }
+
+    roots->head = root_next;
+  }
+
+  stream->root_prev = NULL;
+  stream->root_next = NULL;
+}
+
+void nghttp2_stream_roots_remove_all(nghttp2_stream_roots *roots) {
+  nghttp2_stream *si, *next;
+
+  for (si = roots->head; si;) {
+    next = si->root_next;
+
+    si->root_prev = NULL;
+    si->root_next = NULL;
+
+    si = next;
+  }
+
+  roots->head = NULL;
+}
diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h
new file mode 100644 (file)
index 0000000..50e60a2
--- /dev/null
@@ -0,0 +1,443 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_STREAM_H
+#define NGHTTP2_STREAM_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+#include "nghttp2_outbound_item.h"
+#include "nghttp2_map.h"
+#include "nghttp2_pq.h"
+#include "nghttp2_int.h"
+
+/*
+ * Maximum number of streams in one dependency tree.
+ */
+#define NGHTTP2_MAX_DEP_TREE_LENGTH 100
+
+/*
+ * If local peer is stream initiator:
+ * NGHTTP2_STREAM_OPENING : upon sending request HEADERS
+ * NGHTTP2_STREAM_OPENED : upon receiving response HEADERS
+ * NGHTTP2_STREAM_CLOSING : upon queuing RST_STREAM
+ *
+ * If remote peer is stream initiator:
+ * NGHTTP2_STREAM_OPENING : upon receiving request HEADERS
+ * NGHTTP2_STREAM_OPENED : upon sending response HEADERS
+ * NGHTTP2_STREAM_CLOSING : upon queuing RST_STREAM
+ */
+typedef enum {
+  /* Initial state */
+  NGHTTP2_STREAM_INITIAL,
+  /* For stream initiator: request HEADERS has been sent, but response
+     HEADERS has not been received yet.  For receiver: request HEADERS
+     has been received, but it does not send response HEADERS yet. */
+  NGHTTP2_STREAM_OPENING,
+  /* For stream initiator: response HEADERS is received. For receiver:
+     response HEADERS is sent. */
+  NGHTTP2_STREAM_OPENED,
+  /* RST_STREAM is received, but somehow we need to keep stream in
+     memory. */
+  NGHTTP2_STREAM_CLOSING,
+  /* PUSH_PROMISE is received or sent */
+  NGHTTP2_STREAM_RESERVED,
+  /* Stream is created in this state if it is used as anchor in
+     dependency tree. */
+  NGHTTP2_STREAM_IDLE
+} nghttp2_stream_state;
+
+typedef enum {
+  NGHTTP2_SHUT_NONE = 0,
+  /* Indicates further receptions will be disallowed. */
+  NGHTTP2_SHUT_RD = 0x01,
+  /* Indicates further transmissions will be disallowed. */
+  NGHTTP2_SHUT_WR = 0x02,
+  /* Indicates both further receptions and transmissions will be
+     disallowed. */
+  NGHTTP2_SHUT_RDWR = NGHTTP2_SHUT_RD | NGHTTP2_SHUT_WR
+} nghttp2_shut_flag;
+
+typedef enum {
+  NGHTTP2_STREAM_FLAG_NONE = 0,
+  /* Indicates that this stream is pushed stream */
+  NGHTTP2_STREAM_FLAG_PUSH = 0x01,
+  /* Indicates that this stream was closed */
+  NGHTTP2_STREAM_FLAG_CLOSED = 0x02,
+  /* Indicates the item is deferred due to flow control. */
+  NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL = 0x04,
+  /* Indicates the item is deferred by user callback */
+  NGHTTP2_STREAM_FLAG_DEFERRED_USER = 0x08,
+  /* bitwise OR of NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL and
+     NGHTTP2_STREAM_FLAG_DEFERRED_USER. */
+  NGHTTP2_STREAM_FLAG_DEFERRED_ALL = 0x0c
+
+} nghttp2_stream_flag;
+
+typedef enum {
+  NGHTTP2_STREAM_DPRI_NONE = 0,
+  NGHTTP2_STREAM_DPRI_NO_ITEM = 0x01,
+  NGHTTP2_STREAM_DPRI_TOP = 0x02,
+  NGHTTP2_STREAM_DPRI_REST = 0x04
+} nghttp2_stream_dpri;
+
+struct nghttp2_stream_roots;
+
+typedef struct nghttp2_stream_roots nghttp2_stream_roots;
+
+struct nghttp2_stream;
+
+typedef struct nghttp2_stream nghttp2_stream;
+
+struct nghttp2_stream {
+  /* Intrusive Map */
+  nghttp2_map_entry map_entry;
+  /* pointers to form dependency tree.  If multiple streams depend on
+     a stream, only one stream (left most) has non-NULL dep_prev which
+     points to the stream it depends on. The remaining streams are
+     linked using sib_prev and sib_next.  The stream which has
+     non-NULL dep_prev always NULL sib_prev.  The right most stream
+     has NULL sib_next.  If this stream is a root of dependency tree,
+     dep_prev and sib_prev are NULL. */
+  nghttp2_stream *dep_prev, *dep_next;
+  nghttp2_stream *sib_prev, *sib_next;
+  /* pointers to track dependency tree root streams.  This is
+     doubly-linked list and first element is pointed by
+     roots->head. */
+  nghttp2_stream *root_prev, *root_next;
+  /* When stream is kept after closure, it may be kept in doubly
+     linked list pointed by nghttp2_session closed_stream_head.
+     closed_next points to the next stream object if it is the element
+     of the list. */
+  nghttp2_stream *closed_prev, *closed_next;
+  /* pointer to roots, which tracks dependency tree roots */
+  nghttp2_stream_roots *roots;
+  /* The arbitrary data provided by user for this stream. */
+  void *stream_user_data;
+  /* Item to send */
+  nghttp2_outbound_item *item;
+  /* stream ID */
+  int32_t stream_id;
+  /* categorized priority of this stream.  Only stream bearing
+     NGHTTP2_STREAM_DPRI_TOP can send item. */
+  nghttp2_stream_dpri dpri;
+  /* the number of streams in subtree */
+  size_t num_substreams;
+  /* Current remote window size. This value is computed against the
+     current initial window size of remote endpoint. */
+  int32_t remote_window_size;
+  /* Keep track of the number of bytes received without
+     WINDOW_UPDATE. This could be negative after submitting negative
+     value to WINDOW_UPDATE */
+  int32_t recv_window_size;
+  /* The number of bytes consumed by the application and now is
+     subject to WINDOW_UPDATE.  This is only used when auto
+     WINDOW_UPDATE is turned off. */
+  int32_t consumed_size;
+  /* The amount of recv_window_size cut using submitting negative
+     value to WINDOW_UPDATE */
+  int32_t recv_reduction;
+  /* window size for local flow control. It is initially set to
+     NGHTTP2_INITIAL_WINDOW_SIZE and could be increased/decreased by
+     submitting WINDOW_UPDATE. See nghttp2_submit_window_update(). */
+  int32_t local_window_size;
+  /* weight of this stream */
+  int32_t weight;
+  /* effective weight of this stream in belonging dependency tree */
+  int32_t effective_weight;
+  /* sum of weight (not effective_weight) of direct descendants */
+  int32_t sum_dep_weight;
+  /* sum of weight of direct descendants which have at least one
+     descendant with dpri == NGHTTP2_STREAM_DPRI_TOP.  We use this
+     value to calculate effective weight. */
+  int32_t sum_norest_weight;
+  /* sum of weight of direct descendants whose dpri value is
+     NGHTTP2_STREAM_DPRI_TOP */
+  int32_t sum_top_weight;
+  nghttp2_stream_state state;
+  /* This is bitwise-OR of 0 or more of nghttp2_stream_flag. */
+  uint8_t flags;
+  /* Bitwise OR of zero or more nghttp2_shut_flag values */
+  uint8_t shut_flags;
+};
+
+void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id,
+                         uint8_t flags, nghttp2_stream_state initial_state,
+                         int32_t weight, nghttp2_stream_roots *roots,
+                         int32_t remote_initial_window_size,
+                         int32_t local_initial_window_size,
+                         void *stream_user_data);
+
+void nghttp2_stream_free(nghttp2_stream *stream);
+
+/*
+ * Disallow either further receptions or transmissions, or both.
+ * |flag| is bitwise OR of one or more of nghttp2_shut_flag.
+ */
+void nghttp2_stream_shutdown(nghttp2_stream *stream, nghttp2_shut_flag flag);
+
+/*
+ * Defer |stream->item|.  We won't call this function in the situation
+ * where |stream->item| == NULL.  The |flags| is bitwise OR of zero or
+ * more of NGHTTP2_STREAM_FLAG_DEFERRED_USER and
+ * NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL.  The |flags| indicates
+ * the reason of this action.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags,
+                              nghttp2_session *session);
+
+/*
+ * Put back deferred data in this stream to active state.  The |flags|
+ * are one or more of bitwise OR of the following values:
+ * NGHTTP2_STREAM_FLAG_DEFERRED_USER and
+ * NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL and given masks are
+ * cleared if they are set.  So even if this function is called, if
+ * one of flag is still set, data does not become active.
+ */
+int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags,
+                                        nghttp2_session *session);
+
+/*
+ * Returns nonzero if item is deferred by whatever reason.
+ */
+int nghttp2_stream_check_deferred_item(nghttp2_stream *stream);
+
+/*
+ * Returns nonzero if item is deferred by flow control.
+ */
+int nghttp2_stream_check_deferred_by_flow_control(nghttp2_stream *stream);
+
+/*
+ * Updates the remote window size with the new value
+ * |new_initial_window_size|. The |old_initial_window_size| is used to
+ * calculate the current window size.
+ *
+ * This function returns 0 if it succeeds or -1. The failure is due to
+ * overflow.
+ */
+int nghttp2_stream_update_remote_initial_window_size(
+    nghttp2_stream *stream, int32_t new_initial_window_size,
+    int32_t old_initial_window_size);
+
+/*
+ * Updates the local window size with the new value
+ * |new_initial_window_size|. The |old_initial_window_size| is used to
+ * calculate the current window size.
+ *
+ * This function returns 0 if it succeeds or -1. The failure is due to
+ * overflow.
+ */
+int nghttp2_stream_update_local_initial_window_size(
+    nghttp2_stream *stream, int32_t new_initial_window_size,
+    int32_t old_initial_window_size);
+
+/*
+ * Call this function if promised stream |stream| is replied with
+ * HEADERS.  This function makes the state of the |stream| to
+ * NGHTTP2_STREAM_OPENED.
+ */
+void nghttp2_stream_promise_fulfilled(nghttp2_stream *stream);
+
+/*
+ * Returns the stream positioned in root of the dependency tree the
+ * |stream| belongs to.
+ */
+nghttp2_stream *nghttp2_stream_get_dep_root(nghttp2_stream *stream);
+
+/*
+ * Returns nonzero if |target| is found in subtree of |stream|.
+ */
+int nghttp2_stream_dep_subtree_find(nghttp2_stream *stream,
+                                    nghttp2_stream *target);
+
+/*
+ * Computes distributed weight of a stream of the |weight| under the
+ * |stream| if |stream| is removed from a dependency tree.  The result
+ * is computed using stream->weight rather than
+ * stream->effective_weight.
+ */
+int32_t nghttp2_stream_dep_distributed_weight(nghttp2_stream *stream,
+                                              int32_t weight);
+
+/*
+ * Computes effective weight of a stream of the |weight| under the
+ * |stream|.  The result is computed using stream->effective_weight
+ * rather than stream->weight.  This function is used to determine
+ * weight in dependency tree.
+ */
+int32_t nghttp2_stream_dep_distributed_effective_weight(nghttp2_stream *stream,
+                                                        int32_t weight);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|.  This dependency is
+ * exclusive.  All existing direct descendants of |dep_stream| become
+ * the descendants of the |stream|.  This function assumes
+ * |stream->data| is NULL and no dpri members are changed in this
+ * dependency tree.
+ */
+void nghttp2_stream_dep_insert(nghttp2_stream *dep_stream,
+                               nghttp2_stream *stream);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|.  This dependency is
+ * not exclusive.  This function assumes |stream->data| is NULL and no
+ * dpri members are changed in this dependency tree.
+ */
+void nghttp2_stream_dep_add(nghttp2_stream *dep_stream, nghttp2_stream *stream);
+
+/*
+ * Removes the |stream| from the current dependency tree.  This
+ * function assumes |stream->data| is NULL.
+ */
+void nghttp2_stream_dep_remove(nghttp2_stream *stream);
+
+/*
+ * Attaches |item| to |stream|.  Updates dpri members in this
+ * dependency tree.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_stream_attach_item(nghttp2_stream *stream,
+                               nghttp2_outbound_item *item,
+                               nghttp2_session *session);
+
+/*
+ * Detaches |stream->item|.  Updates dpri members in this dependency
+ * tree.  This function does not free |stream->item|.  The caller must
+ * free it.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_stream_detach_item(nghttp2_stream *stream,
+                               nghttp2_session *session);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|.  This dependency is
+ * exclusive.  Updates dpri members in this dependency tree.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_stream_dep_insert_subtree(nghttp2_stream *dep_stream,
+                                      nghttp2_stream *stream,
+                                      nghttp2_session *session);
+
+/*
+ * Makes the |stream| depend on the |dep_stream|.  This dependency is
+ * not exclusive.  Updates dpri members in this dependency tree.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_stream_dep_add_subtree(nghttp2_stream *dep_stream,
+                                   nghttp2_stream *stream,
+                                   nghttp2_session *session);
+
+/*
+ * Removes subtree whose root stream is |stream|.  Removing subtree
+ * does not change dpri values.  The effective_weight of streams in
+ * removed subtree is not updated.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+void nghttp2_stream_dep_remove_subtree(nghttp2_stream *stream);
+
+/*
+ * Makes the |stream| as root.  Updates dpri members in this
+ * dependency tree.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int nghttp2_stream_dep_make_root(nghttp2_stream *stream,
+                                 nghttp2_session *session);
+
+/*
+ * Makes the |stream| as root and all existing root streams become
+ * direct children of |stream|.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ *     Out of memory
+ */
+int
+nghttp2_stream_dep_all_your_stream_are_belong_to_us(nghttp2_stream *stream,
+                                                    nghttp2_session *session);
+
+/*
+ * Returns nonzero if |stream| is in any dependency tree.
+ */
+int nghttp2_stream_in_dep_tree(nghttp2_stream *stream);
+
+struct nghttp2_stream_roots {
+  nghttp2_stream *head;
+
+  int32_t num_streams;
+};
+
+void nghttp2_stream_roots_init(nghttp2_stream_roots *roots);
+
+void nghttp2_stream_roots_free(nghttp2_stream_roots *roots);
+
+void nghttp2_stream_roots_add(nghttp2_stream_roots *roots,
+                              nghttp2_stream *stream);
+
+void nghttp2_stream_roots_remove(nghttp2_stream_roots *roots,
+                                 nghttp2_stream *stream);
+
+void nghttp2_stream_roots_remove_all(nghttp2_stream_roots *roots);
+
+#endif /* NGHTTP2_STREAM */
diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c
new file mode 100644 (file)
index 0000000..c61982e
--- /dev/null
@@ -0,0 +1,482 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_submit.h"
+
+#include <string.h>
+#include <assert.h>
+
+#include "nghttp2_session.h"
+#include "nghttp2_frame.h"
+#include "nghttp2_helper.h"
+#include "nghttp2_priority_spec.h"
+
+/* This function takes ownership of |nva_copy|. Regardless of the
+   return value, the caller must not free |nva_copy| after this
+   function returns. */
+static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
+                                     int32_t stream_id,
+                                     const nghttp2_priority_spec *pri_spec,
+                                     nghttp2_nv *nva_copy, size_t nvlen,
+                                     const nghttp2_data_provider *data_prd,
+                                     void *stream_user_data,
+                                     uint8_t attach_stream) {
+  int rv;
+  uint8_t flags_copy;
+  nghttp2_outbound_item *item = NULL;
+  nghttp2_frame *frame = NULL;
+  nghttp2_headers_category hcat;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (stream_id == 0) {
+    rv = NGHTTP2_ERR_INVALID_ARGUMENT;
+    goto fail;
+  }
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    rv = NGHTTP2_ERR_NOMEM;
+    goto fail;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  if (data_prd != NULL && data_prd->read_callback != NULL) {
+    item->aux_data.headers.data_prd = *data_prd;
+  }
+
+  item->aux_data.headers.stream_user_data = stream_user_data;
+  item->aux_data.headers.attach_stream = attach_stream;
+
+  flags_copy = (flags & (NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PRIORITY)) |
+               NGHTTP2_FLAG_END_HEADERS;
+
+  if (stream_id == -1) {
+    if (session->next_stream_id > INT32_MAX) {
+      rv = NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE;
+      goto fail;
+    }
+
+    stream_id = session->next_stream_id;
+    session->next_stream_id += 2;
+
+    hcat = NGHTTP2_HCAT_REQUEST;
+  } else {
+    /* More specific categorization will be done later. */
+    hcat = NGHTTP2_HCAT_HEADERS;
+  }
+
+  frame = &item->frame;
+
+  nghttp2_frame_headers_init(&frame->headers, flags_copy, stream_id, hcat,
+                             pri_spec, nva_copy, nvlen);
+
+  rv = nghttp2_session_add_item(session, item);
+
+  if (rv != 0) {
+    nghttp2_frame_headers_free(&frame->headers, mem);
+    goto fail2;
+  }
+
+  if (hcat == NGHTTP2_HCAT_REQUEST) {
+    return stream_id;
+  }
+
+  return 0;
+
+fail:
+  /* nghttp2_frame_headers_init() takes ownership of nva_copy. */
+  nghttp2_nv_array_del(nva_copy, mem);
+fail2:
+  nghttp2_mem_free(mem, item);
+
+  return rv;
+}
+
+static void adjust_priority_spec_weight(nghttp2_priority_spec *pri_spec) {
+  if (pri_spec->weight < NGHTTP2_MIN_WEIGHT) {
+    pri_spec->weight = NGHTTP2_MIN_WEIGHT;
+  } else if (pri_spec->weight > NGHTTP2_MAX_WEIGHT) {
+    pri_spec->weight = NGHTTP2_MAX_WEIGHT;
+  }
+}
+
+static int32_t submit_headers_shared_nva(nghttp2_session *session,
+                                         uint8_t flags, int32_t stream_id,
+                                         const nghttp2_priority_spec *pri_spec,
+                                         const nghttp2_nv *nva, size_t nvlen,
+                                         const nghttp2_data_provider *data_prd,
+                                         void *stream_user_data,
+                                         uint8_t attach_stream) {
+  int rv;
+  nghttp2_nv *nva_copy;
+  nghttp2_priority_spec copy_pri_spec;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (pri_spec) {
+    copy_pri_spec = *pri_spec;
+    adjust_priority_spec_weight(&copy_pri_spec);
+  } else {
+    nghttp2_priority_spec_default_init(&copy_pri_spec);
+  }
+
+  rv = nghttp2_nv_array_copy(&nva_copy, nva, nvlen, mem);
+  if (rv < 0) {
+    return rv;
+  }
+
+  return submit_headers_shared(session, flags, stream_id, &copy_pri_spec,
+                               nva_copy, nvlen, data_prd, stream_user_data,
+                               attach_stream);
+}
+
+int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
+                               int32_t stream_id,
+                               const nghttp2_priority_spec *pri_spec,
+                               const nghttp2_nv *nva, size_t nvlen,
+                               void *stream_user_data) {
+  flags &= NGHTTP2_FLAG_END_STREAM;
+
+  if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec)) {
+    flags |= NGHTTP2_FLAG_PRIORITY;
+  } else {
+    pri_spec = NULL;
+  }
+
+  return submit_headers_shared_nva(session, flags, stream_id, pri_spec, nva,
+                                   nvlen, NULL, stream_user_data, 0);
+}
+
+int nghttp2_submit_ping(nghttp2_session *session, uint8_t flags _U_,
+                        const uint8_t *opaque_data) {
+  return nghttp2_session_add_ping(session, NGHTTP2_FLAG_NONE, opaque_data);
+}
+
+int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags _U_,
+                            int32_t stream_id,
+                            const nghttp2_priority_spec *pri_spec) {
+  int rv;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_priority_spec copy_pri_spec;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (stream_id == 0 || pri_spec == NULL) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (stream_id == pri_spec->stream_id) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  copy_pri_spec = *pri_spec;
+
+  adjust_priority_spec_weight(&copy_pri_spec);
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_priority_init(&frame->priority, stream_id, &copy_pri_spec);
+
+  rv = nghttp2_session_add_item(session, item);
+
+  if (rv != 0) {
+    nghttp2_frame_priority_free(&frame->priority);
+    nghttp2_mem_free(mem, item);
+
+    return rv;
+  }
+
+  return 0;
+}
+
+int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags _U_,
+                              int32_t stream_id, uint32_t error_code) {
+  if (stream_id == 0) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  return nghttp2_session_add_rst_stream(session, stream_id, error_code);
+}
+
+int nghttp2_submit_goaway(nghttp2_session *session, uint8_t flags _U_,
+                          int32_t last_stream_id, uint32_t error_code,
+                          const uint8_t *opaque_data, size_t opaque_data_len) {
+  if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) {
+    return 0;
+  }
+  return nghttp2_session_add_goaway(session, last_stream_id, error_code,
+                                    opaque_data, opaque_data_len,
+                                    NGHTTP2_GOAWAY_AUX_NONE);
+}
+
+int nghttp2_submit_shutdown_notice(nghttp2_session *session) {
+  if (!session->server) {
+    return NGHTTP2_ERR_INVALID_STATE;
+  }
+  if (session->goaway_flags) {
+    return 0;
+  }
+  return nghttp2_session_add_goaway(session, (1u << 31) - 1, NGHTTP2_NO_ERROR,
+                                    NULL, 0,
+                                    NGHTTP2_GOAWAY_AUX_SHUTDOWN_NOTICE);
+}
+
+int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags _U_,
+                            const nghttp2_settings_entry *iv, size_t niv) {
+  return nghttp2_session_add_settings(session, NGHTTP2_FLAG_NONE, iv, niv);
+}
+
+int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags _U_,
+                                    int32_t stream_id, const nghttp2_nv *nva,
+                                    size_t nvlen,
+                                    void *promised_stream_user_data) {
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_nv *nva_copy;
+  uint8_t flags_copy;
+  int32_t promised_stream_id;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (stream_id == 0 || nghttp2_session_is_my_stream_id(session, stream_id)) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (!session->server) {
+    return NGHTTP2_ERR_PROTO;
+  }
+
+  /* All 32bit signed stream IDs are spent. */
+  if (session->next_stream_id > INT32_MAX) {
+    return NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE;
+  }
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  item->aux_data.headers.stream_user_data = promised_stream_user_data;
+
+  frame = &item->frame;
+
+  rv = nghttp2_nv_array_copy(&nva_copy, nva, nvlen, mem);
+  if (rv < 0) {
+    nghttp2_mem_free(mem, item);
+    return rv;
+  }
+
+  flags_copy = NGHTTP2_FLAG_END_HEADERS;
+
+  promised_stream_id = session->next_stream_id;
+  session->next_stream_id += 2;
+
+  nghttp2_frame_push_promise_init(&frame->push_promise, flags_copy, stream_id,
+                                  promised_stream_id, nva_copy, nvlen);
+
+  rv = nghttp2_session_add_item(session, item);
+
+  if (rv != 0) {
+    nghttp2_frame_push_promise_free(&frame->push_promise, mem);
+    nghttp2_mem_free(mem, item);
+
+    return rv;
+  }
+
+  return promised_stream_id;
+}
+
+int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
+                                 int32_t stream_id,
+                                 int32_t window_size_increment) {
+  int rv;
+  nghttp2_stream *stream = 0;
+  if (window_size_increment == 0) {
+    return 0;
+  }
+  flags = 0;
+  if (stream_id == 0) {
+    rv = nghttp2_adjust_local_window_size(
+        &session->local_window_size, &session->recv_window_size,
+        &session->recv_reduction, &window_size_increment);
+    if (rv != 0) {
+      return rv;
+    }
+  } else {
+    stream = nghttp2_session_get_stream(session, stream_id);
+    if (!stream) {
+      return 0;
+    }
+
+    rv = nghttp2_adjust_local_window_size(
+        &stream->local_window_size, &stream->recv_window_size,
+        &stream->recv_reduction, &window_size_increment);
+    if (rv != 0) {
+      return rv;
+    }
+  }
+
+  if (window_size_increment > 0) {
+    if (stream_id == 0) {
+      session->consumed_size =
+          nghttp2_max(0, session->consumed_size - window_size_increment);
+    } else {
+      stream->consumed_size =
+          nghttp2_max(0, stream->consumed_size - window_size_increment);
+    }
+
+    return nghttp2_session_add_window_update(session, flags, stream_id,
+                                             window_size_increment);
+  }
+  return 0;
+}
+
+int nghttp2_submit_altsvc(nghttp2_session *session _U_, uint8_t flags _U_,
+                          int32_t stream_id _U_, uint32_t max_age _U_,
+                          uint16_t port _U_, const uint8_t *protocol_id _U_,
+                          size_t protocol_id_len _U_, const uint8_t *host _U_,
+                          size_t host_len _U_, const uint8_t *origin _U_,
+                          size_t origin_len _U_) {
+  return 0;
+}
+
+static uint8_t set_request_flags(const nghttp2_priority_spec *pri_spec,
+                                 const nghttp2_data_provider *data_prd) {
+  uint8_t flags = NGHTTP2_FLAG_NONE;
+  if (data_prd == NULL || data_prd->read_callback == NULL) {
+    flags |= NGHTTP2_FLAG_END_STREAM;
+  }
+
+  if (pri_spec) {
+    flags |= NGHTTP2_FLAG_PRIORITY;
+  }
+
+  return flags;
+}
+
+int32_t nghttp2_submit_request(nghttp2_session *session,
+                               const nghttp2_priority_spec *pri_spec,
+                               const nghttp2_nv *nva, size_t nvlen,
+                               const nghttp2_data_provider *data_prd,
+                               void *stream_user_data) {
+  uint8_t flags;
+
+  if (pri_spec && nghttp2_priority_spec_check_default(pri_spec)) {
+    pri_spec = NULL;
+  }
+
+  flags = set_request_flags(pri_spec, data_prd);
+
+  return submit_headers_shared_nva(session, flags, -1, pri_spec, nva, nvlen,
+                                   data_prd, stream_user_data, 0);
+}
+
+static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) {
+  uint8_t flags = NGHTTP2_FLAG_NONE;
+  if (data_prd == NULL || data_prd->read_callback == NULL) {
+    flags |= NGHTTP2_FLAG_END_STREAM;
+  }
+  return flags;
+}
+
+int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
+                            const nghttp2_nv *nva, size_t nvlen,
+                            const nghttp2_data_provider *data_prd) {
+  uint8_t flags = set_response_flags(data_prd);
+  return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen,
+                                   data_prd, NULL, 1);
+}
+
+int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
+                        int32_t stream_id,
+                        const nghttp2_data_provider *data_prd) {
+  int rv;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_data_aux_data *aux_data;
+  uint8_t nflags = flags & NGHTTP2_FLAG_END_STREAM;
+  nghttp2_mem *mem;
+
+  mem = &session->mem;
+
+  if (stream_id == 0) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
+  if (item == NULL) {
+    return NGHTTP2_ERR_NOMEM;
+  }
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+  aux_data = &item->aux_data.data;
+  aux_data->data_prd = *data_prd;
+  aux_data->eof = 0;
+  aux_data->flags = nflags;
+
+  /* flags are sent on transmission */
+  nghttp2_frame_data_init(&frame->data, NGHTTP2_FLAG_NONE, stream_id);
+
+  rv = nghttp2_session_add_item(session, item);
+  if (rv != 0) {
+    nghttp2_frame_data_free(&frame->data);
+    nghttp2_mem_free(mem, item);
+    return rv;
+  }
+  return 0;
+}
+
+ssize_t nghttp2_pack_settings_payload(uint8_t *buf, size_t buflen,
+                                      const nghttp2_settings_entry *iv,
+                                      size_t niv) {
+  if (!nghttp2_iv_check(iv, niv)) {
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+  }
+
+  if (buflen < (niv * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH)) {
+    return NGHTTP2_ERR_INSUFF_BUFSIZE;
+  }
+
+  return nghttp2_frame_pack_settings_payload(buf, iv, niv);
+}
diff --git a/lib/nghttp2_submit.h b/lib/nghttp2_submit.h
new file mode 100644 (file)
index 0000000..545388c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_SUBMIT_H
+#define NGHTTP2_SUBMIT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+#endif /* NGHTTP2_SUBMIT_H */
diff --git a/lib/nghttp2_version.c b/lib/nghttp2_version.c
new file mode 100644 (file)
index 0000000..8c5710d
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012, 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <nghttp2/nghttp2.h>
+
+static nghttp2_info version = {NGHTTP2_VERSION_AGE, NGHTTP2_VERSION_NUM,
+                               NGHTTP2_VERSION, NGHTTP2_PROTO_VERSION_ID};
+
+nghttp2_info *nghttp2_version(int least_version) {
+  if (least_version > NGHTTP2_VERSION_NUM)
+    return NULL;
+  return &version;
+}
diff --git a/m4/ax_boost_asio.m4 b/m4/ax_boost_asio.m4
new file mode 100644 (file)
index 0000000..b57d487
--- /dev/null
@@ -0,0 +1,110 @@
+# ===========================================================================
+#       http://www.gnu.org/software/autoconf-archive/ax_boost_asio.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_BOOST_ASIO
+#
+# DESCRIPTION
+#
+#   Test for Asio library from the Boost C++ libraries. The macro requires a
+#   preceding call to AX_BOOST_BASE. Further documentation is available at
+#   <http://randspringer.de/boost/index.html>.
+#
+#   This macro calls:
+#
+#     AC_SUBST(BOOST_ASIO_LIB)
+#
+#   And sets:
+#
+#     HAVE_BOOST_ASIO
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Thomas Porschberg <thomas@randspringer.de>
+#   Copyright (c) 2008 Pete Greenwell <pete@mu.org>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 16
+
+AC_DEFUN([AX_BOOST_ASIO],
+[
+       AC_ARG_WITH([boost-asio],
+       AS_HELP_STRING([--with-boost-asio@<:@=special-lib@:>@],
+                   [use the ASIO library from boost - it is possible to specify a certain library for the linker
+                        e.g. --with-boost-asio=boost_system-gcc41-mt-1_34 ]),
+        [
+        if test "$withval" = "no"; then
+                       want_boost="no"
+        elif test "$withval" = "yes"; then
+            want_boost="yes"
+            ax_boost_user_asio_lib=""
+        else
+                   want_boost="yes"
+               ax_boost_user_asio_lib="$withval"
+               fi
+        ],
+        [want_boost="yes"]
+       )
+
+       if test "x$want_boost" = "xyes"; then
+        AC_REQUIRE([AC_PROG_CC])
+               CPPFLAGS_SAVED="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+               export CPPFLAGS
+
+               LDFLAGS_SAVED="$LDFLAGS"
+               LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+               export LDFLAGS
+
+        AC_CACHE_CHECK(whether the Boost::ASIO library is available,
+                                          ax_cv_boost_asio,
+        [AC_LANG_PUSH([C++])
+                AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ @%:@include <boost/asio.hpp>
+                                                                                       ]],
+                                  [[
+
+                                    boost::asio::io_service io;
+                                    boost::system::error_code timer_result;
+                                    boost::asio::deadline_timer t(io);
+                                    t.cancel();
+                                    io.run_one();
+                                                                       return 0;
+                                   ]])],
+                             ax_cv_boost_asio=yes, ax_cv_boost_asio=no)
+         AC_LANG_POP([C++])
+               ])
+               if test "x$ax_cv_boost_asio" = "xyes"; then
+                       AC_DEFINE(HAVE_BOOST_ASIO,,[define if the Boost::ASIO library is available])
+                       BN=boost_system
+                       BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'`
+            if test "x$ax_boost_user_asio_lib" = "x"; then
+                               for ax_lib in `ls $BOOSTLIBDIR/libboost_system*.so* $BOOSTLIBDIR/libboost_system*.dylib* $BOOSTLIBDIR/libboost_system*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_system.*\)\.so.*$;\1;' -e 's;^lib\(boost_system.*\)\.dylib.*$;\1;' -e 's;^lib\(boost_system.*\)\.a.*$;\1;' ` ; do
+                                   AC_CHECK_LIB($ax_lib, main, [BOOST_ASIO_LIB="-l$ax_lib" AC_SUBST(BOOST_ASIO_LIB) link_thread="yes" break],
+                                 [link_thread="no"])
+                               done
+            else
+               for ax_lib in $ax_boost_user_asio_lib $BN-$ax_boost_user_asio_lib; do
+                                     AC_CHECK_LIB($ax_lib, main,
+                                   [BOOST_ASIO_LIB="-l$ax_lib" AC_SUBST(BOOST_ASIO_LIB) link_asio="yes" break],
+                                   [link_asio="no"])
+                  done
+
+            fi
+            if test "x$ax_lib" = "x"; then
+                AC_MSG_ERROR(Could not find a version of the library!)
+            fi
+                       if test "x$link_asio" = "xno"; then
+                               AC_MSG_ERROR(Could not link against $ax_lib !)
+                       fi
+               fi
+
+               CPPFLAGS="$CPPFLAGS_SAVED"
+       LDFLAGS="$LDFLAGS_SAVED"
+       fi
+])
diff --git a/m4/ax_boost_base.m4 b/m4/ax_boost_base.m4
new file mode 100644 (file)
index 0000000..d7c9d0d
--- /dev/null
@@ -0,0 +1,275 @@
+# ===========================================================================
+#       http://www.gnu.org/software/autoconf-archive/ax_boost_base.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_BOOST_BASE([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# DESCRIPTION
+#
+#   Test for the Boost C++ libraries of a particular version (or newer)
+#
+#   If no path to the installed boost library is given the macro searchs
+#   under /usr, /usr/local, /opt and /opt/local and evaluates the
+#   $BOOST_ROOT environment variable. Further documentation is available at
+#   <http://randspringer.de/boost/index.html>.
+#
+#   This macro calls:
+#
+#     AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS)
+#
+#   And sets:
+#
+#     HAVE_BOOST
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Thomas Porschberg <thomas@randspringer.de>
+#   Copyright (c) 2009 Peter Adolphs
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 25
+
+AC_DEFUN([AX_BOOST_BASE],
+[
+AC_ARG_WITH([boost],
+  [AS_HELP_STRING([--with-boost@<:@=ARG@:>@],
+    [use Boost library from a standard location (ARG=yes),
+     from the specified location (ARG=<path>),
+     or disable it (ARG=no)
+     @<:@ARG=yes@:>@ ])],
+    [
+    if test "$withval" = "no"; then
+        want_boost="no"
+    elif test "$withval" = "yes"; then
+        want_boost="yes"
+        ac_boost_path=""
+    else
+        want_boost="yes"
+        ac_boost_path="$withval"
+    fi
+    ],
+    [want_boost="yes"])
+
+
+AC_ARG_WITH([boost-libdir],
+        AS_HELP_STRING([--with-boost-libdir=LIB_DIR],
+        [Force given directory for boost libraries. Note that this will override library path detection, so use this parameter only if default library detection fails and you know exactly where your boost libraries are located.]),
+        [
+        if test -d "$withval"
+        then
+                ac_boost_lib_path="$withval"
+        else
+                AC_MSG_ERROR(--with-boost-libdir expected directory name)
+        fi
+        ],
+        [ac_boost_lib_path=""]
+)
+
+if test "x$want_boost" = "xyes"; then
+    boost_lib_version_req=ifelse([$1], ,1.20.0,$1)
+    boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'`
+    boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'`
+    boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'`
+    boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
+    if test "x$boost_lib_version_req_sub_minor" = "x" ; then
+        boost_lib_version_req_sub_minor="0"
+        fi
+    WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+  $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor`
+    AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req)
+    succeeded=no
+
+    dnl On 64-bit systems check for system libraries in both lib64 and lib.
+    dnl The former is specified by FHS, but e.g. Debian does not adhere to
+    dnl this (as it rises problems for generic multi-arch support).
+    dnl The last entry in the list is chosen by default when no libraries
+    dnl are found, e.g. when only header-only libraries are installed!
+    libsubdirs="lib"
+    ax_arch=`uname -m`
+    case $ax_arch in
+      x86_64)
+        libsubdirs="lib64 libx32 lib lib64"
+        ;;
+      ppc64|s390x|sparc64|aarch64|ppc64le)
+        libsubdirs="lib64 lib lib64 ppc64le"
+        ;;
+    esac
+
+    dnl allow for real multi-arch paths e.g. /usr/lib/x86_64-linux-gnu. Give
+    dnl them priority over the other paths since, if libs are found there, they
+    dnl are almost assuredly the ones desired.
+    AC_REQUIRE([AC_CANONICAL_HOST])
+    libsubdirs="lib/${host_cpu}-${host_os} $libsubdirs"
+
+    case ${host_cpu} in
+      i?86)
+        libsubdirs="lib/i386-${host_os} $libsubdirs"
+        ;;
+    esac
+
+    dnl first we check the system location for boost libraries
+    dnl this location ist chosen if boost libraries are installed with the --layout=system option
+    dnl or if you install boost with RPM
+    if test "$ac_boost_path" != ""; then
+        BOOST_CPPFLAGS="-I$ac_boost_path/include"
+        for ac_boost_path_tmp in $libsubdirs; do
+                if test -d "$ac_boost_path"/"$ac_boost_path_tmp" ; then
+                        BOOST_LDFLAGS="-L$ac_boost_path/$ac_boost_path_tmp"
+                        break
+                fi
+        done
+    elif test "$cross_compiling" != yes; then
+        for ac_boost_path_tmp in /usr /usr/local /opt /opt/local ; do
+            if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then
+                for libsubdir in $libsubdirs ; do
+                    if ls "$ac_boost_path_tmp/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
+                done
+                BOOST_LDFLAGS="-L$ac_boost_path_tmp/$libsubdir"
+                BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include"
+                break;
+            fi
+        done
+    fi
+
+    dnl overwrite ld flags if we have required special directory with
+    dnl --with-boost-libdir parameter
+    if test "$ac_boost_lib_path" != ""; then
+       BOOST_LDFLAGS="-L$ac_boost_lib_path"
+    fi
+
+    CPPFLAGS_SAVED="$CPPFLAGS"
+    CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+    export CPPFLAGS
+
+    LDFLAGS_SAVED="$LDFLAGS"
+    LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+    export LDFLAGS
+
+    AC_REQUIRE([AC_PROG_CXX])
+    AC_LANG_PUSH(C++)
+        AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+    @%:@include <boost/version.hpp>
+    ]], [[
+    #if BOOST_VERSION >= $WANT_BOOST_VERSION
+    // Everything is okay
+    #else
+    #  error Boost version is too old
+    #endif
+    ]])],[
+        AC_MSG_RESULT(yes)
+    succeeded=yes
+    found_system=yes
+        ],[
+        ])
+    AC_LANG_POP([C++])
+
+
+
+    dnl if we found no boost with system layout we search for boost libraries
+    dnl built and installed without the --layout=system option or for a staged(not installed) version
+    if test "x$succeeded" != "xyes"; then
+        _version=0
+        if test "$ac_boost_path" != ""; then
+            if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then
+                for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do
+                    _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
+                    V_CHECK=`expr $_version_tmp \> $_version`
+                    if test "$V_CHECK" = "1" ; then
+                        _version=$_version_tmp
+                    fi
+                    VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
+                    BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE"
+                done
+            fi
+        else
+            if test "$cross_compiling" != yes; then
+                for ac_boost_path in /usr /usr/local /opt /opt/local ; do
+                    if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then
+                        for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do
+                            _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
+                            V_CHECK=`expr $_version_tmp \> $_version`
+                            if test "$V_CHECK" = "1" ; then
+                                _version=$_version_tmp
+                                best_path=$ac_boost_path
+                            fi
+                        done
+                    fi
+                done
+
+                VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
+                BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE"
+                if test "$ac_boost_lib_path" = ""; then
+                    for libsubdir in $libsubdirs ; do
+                        if ls "$best_path/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
+                    done
+                    BOOST_LDFLAGS="-L$best_path/$libsubdir"
+                fi
+            fi
+
+            if test "x$BOOST_ROOT" != "x"; then
+                for libsubdir in $libsubdirs ; do
+                    if ls "$BOOST_ROOT/stage/$libsubdir/libboost_"* >/dev/null 2>&1 ; then break; fi
+                done
+                if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/$libsubdir" && test -r "$BOOST_ROOT/stage/$libsubdir"; then
+                    version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'`
+                    stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'`
+                        stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'`
+                    V_CHECK=`expr $stage_version_shorten \>\= $_version`
+                    if test "$V_CHECK" = "1" -a "$ac_boost_lib_path" = "" ; then
+                        AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT)
+                        BOOST_CPPFLAGS="-I$BOOST_ROOT"
+                        BOOST_LDFLAGS="-L$BOOST_ROOT/stage/$libsubdir"
+                    fi
+                fi
+            fi
+        fi
+
+        CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+        export CPPFLAGS
+        LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+        export LDFLAGS
+
+        AC_LANG_PUSH(C++)
+            AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+        @%:@include <boost/version.hpp>
+        ]], [[
+        #if BOOST_VERSION >= $WANT_BOOST_VERSION
+        // Everything is okay
+        #else
+        #  error Boost version is too old
+        #endif
+        ]])],[
+            AC_MSG_RESULT(yes)
+        succeeded=yes
+        found_system=yes
+            ],[
+            ])
+        AC_LANG_POP([C++])
+    fi
+
+    if test "$succeeded" != "yes" ; then
+        if test "$_version" = "0" ; then
+            AC_MSG_NOTICE([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option.  If you are sure you have boost installed, then check your version number looking in <boost/version.hpp>. See http://randspringer.de/boost for more documentation.]])
+        else
+            AC_MSG_NOTICE([Your boost libraries seems to old (version $_version).])
+        fi
+        # execute ACTION-IF-NOT-FOUND (if present):
+        ifelse([$3], , :, [$3])
+    else
+        AC_SUBST(BOOST_CPPFLAGS)
+        AC_SUBST(BOOST_LDFLAGS)
+        AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available])
+        # execute ACTION-IF-FOUND (if present):
+        ifelse([$2], , :, [$2])
+    fi
+
+    CPPFLAGS="$CPPFLAGS_SAVED"
+    LDFLAGS="$LDFLAGS_SAVED"
+fi
+
+])
diff --git a/m4/ax_boost_system.m4 b/m4/ax_boost_system.m4
new file mode 100644 (file)
index 0000000..c4c4555
--- /dev/null
@@ -0,0 +1,120 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_boost_system.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_BOOST_SYSTEM
+#
+# DESCRIPTION
+#
+#   Test for System library from the Boost C++ libraries. The macro requires
+#   a preceding call to AX_BOOST_BASE. Further documentation is available at
+#   <http://randspringer.de/boost/index.html>.
+#
+#   This macro calls:
+#
+#     AC_SUBST(BOOST_SYSTEM_LIB)
+#
+#   And sets:
+#
+#     HAVE_BOOST_SYSTEM
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Thomas Porschberg <thomas@randspringer.de>
+#   Copyright (c) 2008 Michael Tindal
+#   Copyright (c) 2008 Daniel Casimiro <dan.casimiro@gmail.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 17
+
+AC_DEFUN([AX_BOOST_SYSTEM],
+[
+       AC_ARG_WITH([boost-system],
+       AS_HELP_STRING([--with-boost-system@<:@=special-lib@:>@],
+                   [use the System library from boost - it is possible to specify a certain library for the linker
+                        e.g. --with-boost-system=boost_system-gcc-mt ]),
+        [
+        if test "$withval" = "no"; then
+                       want_boost="no"
+        elif test "$withval" = "yes"; then
+            want_boost="yes"
+            ax_boost_user_system_lib=""
+        else
+                   want_boost="yes"
+               ax_boost_user_system_lib="$withval"
+               fi
+        ],
+        [want_boost="yes"]
+       )
+
+       if test "x$want_boost" = "xyes"; then
+        AC_REQUIRE([AC_PROG_CC])
+        AC_REQUIRE([AC_CANONICAL_BUILD])
+               CPPFLAGS_SAVED="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+               export CPPFLAGS
+
+               LDFLAGS_SAVED="$LDFLAGS"
+               LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+               export LDFLAGS
+
+        AC_CACHE_CHECK(whether the Boost::System library is available,
+                                          ax_cv_boost_system,
+        [AC_LANG_PUSH([C++])
+                        CXXFLAGS_SAVE=$CXXFLAGS
+
+                        AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <boost/system/error_code.hpp>]],
+                                   [[boost::system::system_category]])],
+                   ax_cv_boost_system=yes, ax_cv_boost_system=no)
+                        CXXFLAGS=$CXXFLAGS_SAVE
+             AC_LANG_POP([C++])
+               ])
+               if test "x$ax_cv_boost_system" = "xyes"; then
+                       AC_SUBST(BOOST_CPPFLAGS)
+
+                       AC_DEFINE(HAVE_BOOST_SYSTEM,,[define if the Boost::System library is available])
+            BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'`
+
+                       LDFLAGS_SAVE=$LDFLAGS
+            if test "x$ax_boost_user_system_lib" = "x"; then
+                for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do
+                     ax_lib=${libextension}
+                                   AC_CHECK_LIB($ax_lib, exit,
+                                 [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break],
+                                 [link_system="no"])
+                               done
+                if test "x$link_system" != "xyes"; then
+                for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do
+                     ax_lib=${libextension}
+                                   AC_CHECK_LIB($ax_lib, exit,
+                                 [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break],
+                                 [link_system="no"])
+                               done
+                fi
+
+            else
+               for ax_lib in $ax_boost_user_system_lib boost_system-$ax_boost_user_system_lib; do
+                                     AC_CHECK_LIB($ax_lib, exit,
+                                   [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break],
+                                   [link_system="no"])
+                  done
+
+            fi
+            if test "x$ax_lib" = "x"; then
+                AC_MSG_ERROR(Could not find a version of the library!)
+            fi
+                       if test "x$link_system" = "xno"; then
+                               AC_MSG_ERROR(Could not link against $ax_lib !)
+                       fi
+               fi
+
+               CPPFLAGS="$CPPFLAGS_SAVED"
+       LDFLAGS="$LDFLAGS_SAVED"
+       fi
+])
diff --git a/m4/ax_boost_thread.m4 b/m4/ax_boost_thread.m4
new file mode 100644 (file)
index 0000000..79e12cd
--- /dev/null
@@ -0,0 +1,149 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_boost_thread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_BOOST_THREAD
+#
+# DESCRIPTION
+#
+#   Test for Thread library from the Boost C++ libraries. The macro requires
+#   a preceding call to AX_BOOST_BASE. Further documentation is available at
+#   <http://randspringer.de/boost/index.html>.
+#
+#   This macro calls:
+#
+#     AC_SUBST(BOOST_THREAD_LIB)
+#
+#   And sets:
+#
+#     HAVE_BOOST_THREAD
+#
+# LICENSE
+#
+#   Copyright (c) 2009 Thomas Porschberg <thomas@randspringer.de>
+#   Copyright (c) 2009 Michael Tindal
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 27
+
+AC_DEFUN([AX_BOOST_THREAD],
+[
+       AC_ARG_WITH([boost-thread],
+       AS_HELP_STRING([--with-boost-thread@<:@=special-lib@:>@],
+                   [use the Thread library from boost - it is possible to specify a certain library for the linker
+                        e.g. --with-boost-thread=boost_thread-gcc-mt ]),
+        [
+        if test "$withval" = "no"; then
+                       want_boost="no"
+        elif test "$withval" = "yes"; then
+            want_boost="yes"
+            ax_boost_user_thread_lib=""
+        else
+                   want_boost="yes"
+               ax_boost_user_thread_lib="$withval"
+               fi
+        ],
+        [want_boost="yes"]
+       )
+
+       if test "x$want_boost" = "xyes"; then
+        AC_REQUIRE([AC_PROG_CC])
+        AC_REQUIRE([AC_CANONICAL_BUILD])
+               CPPFLAGS_SAVED="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+               export CPPFLAGS
+
+               LDFLAGS_SAVED="$LDFLAGS"
+               LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+               export LDFLAGS
+
+        AC_CACHE_CHECK(whether the Boost::Thread library is available,
+                                          ax_cv_boost_thread,
+        [AC_LANG_PUSH([C++])
+                        CXXFLAGS_SAVE=$CXXFLAGS
+
+                        if test "x$host_os" = "xsolaris" ; then
+                                CXXFLAGS="-pthreads $CXXFLAGS"
+                        elif test "x$host_os" = "xmingw32" ; then
+                                CXXFLAGS="-mthreads $CXXFLAGS"
+                        else
+                               CXXFLAGS="-pthread $CXXFLAGS"
+                        fi
+                        AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <boost/thread/thread.hpp>]],
+                                   [[boost::thread_group thrds;
+                                   return 0;]])],
+                   ax_cv_boost_thread=yes, ax_cv_boost_thread=no)
+                        CXXFLAGS=$CXXFLAGS_SAVE
+             AC_LANG_POP([C++])
+               ])
+               if test "x$ax_cv_boost_thread" = "xyes"; then
+           if test "x$host_os" = "xsolaris" ; then
+                         BOOST_CPPFLAGS="-pthreads $BOOST_CPPFLAGS"
+                  elif test "x$host_os" = "xmingw32" ; then
+                         BOOST_CPPFLAGS="-mthreads $BOOST_CPPFLAGS"
+                  else
+                         BOOST_CPPFLAGS="-pthread $BOOST_CPPFLAGS"
+                  fi
+
+                       AC_SUBST(BOOST_CPPFLAGS)
+
+                       AC_DEFINE(HAVE_BOOST_THREAD,,[define if the Boost::Thread library is available])
+            BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'`
+
+                       LDFLAGS_SAVE=$LDFLAGS
+                        case "x$host_os" in
+                          *bsd* )
+                               LDFLAGS="-pthread $LDFLAGS"
+                          break;
+                          ;;
+                        esac
+            if test "x$ax_boost_user_thread_lib" = "x"; then
+                for libextension in `ls -r $BOOSTLIBDIR/libboost_thread* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'`; do
+                     ax_lib=${libextension}
+                                   AC_CHECK_LIB($ax_lib, exit,
+                                 [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break],
+                                 [link_thread="no"])
+                               done
+                if test "x$link_thread" != "xyes"; then
+                for libextension in `ls -r $BOOSTLIBDIR/boost_thread* 2>/dev/null | sed 's,.*/,,' | sed 's,\..*,,'`; do
+                     ax_lib=${libextension}
+                                   AC_CHECK_LIB($ax_lib, exit,
+                                 [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break],
+                                 [link_thread="no"])
+                               done
+                fi
+
+            else
+               for ax_lib in $ax_boost_user_thread_lib boost_thread-$ax_boost_user_thread_lib; do
+                                     AC_CHECK_LIB($ax_lib, exit,
+                                   [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break],
+                                   [link_thread="no"])
+                  done
+
+            fi
+            if test "x$ax_lib" = "x"; then
+                AC_MSG_ERROR(Could not find a version of the library!)
+            fi
+                       if test "x$link_thread" = "xno"; then
+                               AC_MSG_ERROR(Could not link against $ax_lib !)
+                        else
+                           case "x$host_os" in
+                              *bsd* )
+                               BOOST_LDFLAGS="-pthread $BOOST_LDFLAGS"
+                              break;
+                              ;;
+                           esac
+
+                       fi
+               fi
+
+               CPPFLAGS="$CPPFLAGS_SAVED"
+       LDFLAGS="$LDFLAGS_SAVED"
+       fi
+])
diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4
new file mode 100644 (file)
index 0000000..51df0c0
--- /dev/null
@@ -0,0 +1,74 @@
+# ===========================================================================
+#   http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
+#
+# DESCRIPTION
+#
+#   Check whether the given FLAG works with the current language's compiler
+#   or gives an error.  (Warnings, however, are ignored)
+#
+#   ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
+#   success/failure.
+#
+#   If EXTRA-FLAGS is defined, it is added to the current language's default
+#   flags (e.g. CFLAGS) when the check is done.  The check is thus made with
+#   the flags: "CFLAGS EXTRA-FLAGS FLAG".  This can for example be used to
+#   force the compiler to issue an error when a bad flag is given.
+#
+#   INPUT gives an alternative input source to AC_COMPILE_IFELSE.
+#
+#   NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
+#   macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
+#   Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
+#
+#   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/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 3
+
+AC_DEFUN([AX_CHECK_COMPILE_FLAG],
+[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
+AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
+AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
+  ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
+  _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
+  AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
+    [AS_VAR_SET(CACHEVAR,[yes])],
+    [AS_VAR_SET(CACHEVAR,[no])])
+  _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
+AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
+  [m4_default([$2], :)],
+  [m4_default([$3], :)])
+AS_VAR_POPDEF([CACHEVAR])dnl
+])dnl AX_CHECK_COMPILE_FLAGS
diff --git a/m4/ax_cxx_compile_stdcxx_11.m4 b/m4/ax_cxx_compile_stdcxx_11.m4
new file mode 100644 (file)
index 0000000..af37acd
--- /dev/null
@@ -0,0 +1,133 @@
+# ============================================================================
+#  http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html
+# ============================================================================
+#
+# SYNOPSIS
+#
+#   AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional])
+#
+# DESCRIPTION
+#
+#   Check for baseline language coverage in the compiler for the C++11
+#   standard; if necessary, add switches to CXXFLAGS to enable support.
+#
+#   The first argument, if specified, indicates whether you insist on an
+#   extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+#   -std=c++11).  If neither is specified, you get whatever works, with
+#   preference for an extended mode.
+#
+#   The second argument, if specified 'mandatory' or if left unspecified,
+#   indicates that baseline C++11 support is required and that the macro
+#   should error out if no mode with that support is found.  If specified
+#   'optional', then configuration proceeds regardless, after defining
+#   HAVE_CXX11 if and only if a supporting mode is found.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+#   Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+#   Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 3
+
+m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+])
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl
+  m4_if([$1], [], [],
+        [$1], [ext], [],
+        [$1], [noext], [],
+        [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl
+  m4_if([$2], [], [ax_cxx_compile_cxx11_required=true],
+        [$2], [mandatory], [ax_cxx_compile_cxx11_required=true],
+        [$2], [optional], [ax_cxx_compile_cxx11_required=false],
+        [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])])dnl
+  AC_LANG_PUSH([C++])dnl
+  ac_success=no
+  AC_CACHE_CHECK(whether $CXX supports C++11 features by default,
+  ax_cv_cxx_compile_cxx11,
+  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+    [ax_cv_cxx_compile_cxx11=yes],
+    [ax_cv_cxx_compile_cxx11=no])])
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+  m4_if([$1], [noext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=gnu++11 -std=gnu++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+
+  m4_if([$1], [ext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+  AC_LANG_POP([C++])
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.])
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      AC_MSG_NOTICE([No compiler with C++11 support was found])
+    else
+      HAVE_CXX11=1
+      AC_DEFINE(HAVE_CXX11,1,
+                [define if the compiler supports basic C++11 syntax])
+    fi
+
+    AC_SUBST(HAVE_CXX11)
+  fi
+])
diff --git a/m4/ax_have_epoll.m4 b/m4/ax_have_epoll.m4
new file mode 100644 (file)
index 0000000..07ceb49
--- /dev/null
@@ -0,0 +1,104 @@
+# ===========================================================================
+#       http://www.gnu.org/software/autoconf-archive/ax_have_epoll.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_HAVE_EPOLL([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#   AX_HAVE_EPOLL_PWAIT([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# DESCRIPTION
+#
+#   This macro determines whether the system supports the epoll I/O event
+#   interface. A neat usage example would be:
+#
+#     AX_HAVE_EPOLL(
+#       [AX_CONFIG_FEATURE_ENABLE(epoll)],
+#       [AX_CONFIG_FEATURE_DISABLE(epoll)])
+#     AX_CONFIG_FEATURE(
+#       [epoll], [This platform supports epoll(7)],
+#       [HAVE_EPOLL], [This platform supports epoll(7).])
+#
+#   The epoll interface was added to the Linux kernel in version 2.5.45, and
+#   the macro verifies that a kernel newer than this is installed. This
+#   check is somewhat unreliable if <linux/version.h> doesn't match the
+#   running kernel, but it is necessary regardless, because glibc comes with
+#   stubs for the epoll_create(), epoll_wait(), etc. that allow programs to
+#   compile and link even if the kernel is too old; the problem would then
+#   be detected only at runtime.
+#
+#   Linux kernel version 2.6.19 adds the epoll_pwait() call in addition to
+#   epoll_wait(). The availability of that function can be tested with the
+#   second macro. Generally speaking, it is safe to assume that
+#   AX_HAVE_EPOLL would succeed if AX_HAVE_EPOLL_PWAIT has, but not the
+#   other way round.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Peter Simons <simons@cryp.to>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 10
+
+AC_DEFUN([AX_HAVE_EPOLL], [dnl
+  ax_have_epoll_cppflags="${CPPFLAGS}"
+  AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"])
+  AC_MSG_CHECKING([for Linux epoll(7) interface])
+  AC_CACHE_VAL([ax_cv_have_epoll], [dnl
+    AC_LINK_IFELSE([dnl
+      AC_LANG_PROGRAM([dnl
+#include <sys/epoll.h>
+#ifdef HAVE_LINUX_VERSION_H
+#  include <linux/version.h>
+#  if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45)
+#    error linux kernel version is too old to have epoll
+#  endif
+#endif
+], [dnl
+int fd, rc;
+struct epoll_event ev;
+fd = epoll_create(128);
+rc = epoll_wait(fd, &ev, 1, 0);])],
+      [ax_cv_have_epoll=yes],
+      [ax_cv_have_epoll=no])])
+  CPPFLAGS="${ax_have_epoll_cppflags}"
+  AS_IF([test "${ax_cv_have_epoll}" = "yes"],
+    [AC_MSG_RESULT([yes])
+$1],[AC_MSG_RESULT([no])
+$2])
+])dnl
+
+AC_DEFUN([AX_HAVE_EPOLL_PWAIT], [dnl
+  ax_have_epoll_cppflags="${CPPFLAGS}"
+  AC_CHECK_HEADER([linux/version.h],
+    [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"])
+  AC_MSG_CHECKING([for Linux epoll(7) interface with signals extension])
+  AC_CACHE_VAL([ax_cv_have_epoll_pwait], [dnl
+    AC_LINK_IFELSE([dnl
+      AC_LANG_PROGRAM([dnl
+#ifdef HAVE_LINUX_VERSION_H
+#  include <linux/version.h>
+#  if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+#    error linux kernel version is too old to have epoll_pwait
+#  endif
+#endif
+#include <sys/epoll.h>
+#include <signal.h>
+], [dnl
+int fd, rc;
+struct epoll_event ev;
+fd = epoll_create(128);
+rc = epoll_wait(fd, &ev, 1, 0);
+rc = epoll_pwait(fd, &ev, 1, 0, (sigset_t const *)(0));])],
+      [ax_cv_have_epoll_pwait=yes],
+      [ax_cv_have_epoll_pwait=no])])
+  CPPFLAGS="${ax_have_epoll_cppflags}"
+  AS_IF([test "${ax_cv_have_epoll_pwait}" = "yes"],
+    [AC_MSG_RESULT([yes])
+$1],[AC_MSG_RESULT([no])
+$2])
+])dnl
diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4
new file mode 100644 (file)
index 0000000..067fbcf
--- /dev/null
@@ -0,0 +1,344 @@
+# ===========================================================================
+#      http://www.gnu.org/software/autoconf-archive/ax_python_devel.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PYTHON_DEVEL([version])
+#
+# DESCRIPTION
+#
+#   Note: Defines as a precious variable "PYTHON_VERSION". Don't override it
+#   in your configure.ac.
+#
+#   This macro checks for Python and tries to get the include path to
+#   'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS)
+#   output variables. It also exports $(PYTHON_EXTRA_LIBS) and
+#   $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code.
+#
+#   You can search for some particular version of Python by passing a
+#   parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please
+#   note that you *have* to pass also an operator along with the version to
+#   match, and pay special attention to the single quotes surrounding the
+#   version number. Don't use "PYTHON_VERSION" for this: that environment
+#   variable is declared as precious and thus reserved for the end-user.
+#
+#   This macro should work for all versions of Python >= 2.1.0. As an end
+#   user, you can disable the check for the python version by setting the
+#   PYTHON_NOVERSIONCHECK environment variable to something else than the
+#   empty string.
+#
+#   If you need to use this macro for an older Python version, please
+#   contact the authors. We're always open for feedback.
+#
+# LICENSE
+#
+#   Copyright (c) 2009 Sebastian Huber <sebastian-huber@web.de>
+#   Copyright (c) 2009 Alan W. Irwin
+#   Copyright (c) 2009 Rafael Laboissiere <rafael@laboissiere.net>
+#   Copyright (c) 2009 Andrew Collier
+#   Copyright (c) 2009 Matteo Settenvini <matteo@member.fsf.org>
+#   Copyright (c) 2009 Horst Knorr <hk_classes@knoda.org>
+#   Copyright (c) 2013 Daniel Mullner <muellner@math.stanford.edu>
+#
+#   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/>.
+#
+#   As a special exception, the respective Autoconf Macro's copyright owner
+#   gives unlimited permission to copy, distribute and modify the configure
+#   scripts that are the output of Autoconf when processing the Macro. You
+#   need not follow the terms of the GNU General Public License when using
+#   or distributing such scripts, even though portions of the text of the
+#   Macro appear in them. The GNU General Public License (GPL) does govern
+#   all other use of the material that constitutes the Autoconf Macro.
+#
+#   This special exception to the GPL applies to versions of the Autoconf
+#   Macro released by the Autoconf Archive. When you make and distribute a
+#   modified version of the Autoconf Macro, you may extend this special
+#   exception to the GPL to apply to your modified version as well.
+
+#serial 16
+
+AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL])
+AC_DEFUN([AX_PYTHON_DEVEL],[
+       #
+       # Allow the use of a (user set) custom python version
+       #
+       AC_ARG_VAR([PYTHON_VERSION],[The installed Python
+               version to use, for example '2.3'. This string
+               will be appended to the Python interpreter
+               canonical name.])
+
+       AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]])
+       if test -z "$PYTHON"; then
+          AC_MSG_WARN([Cannot find python$PYTHON_VERSION in your system path])
+          PYTHON_VERSION=""
+           no_python_devel=yes
+       fi
+
+AS_IF([test -z "$no_python_devel"], [
+       #
+       # Check for a version of Python >= 2.1.0
+       #
+       AC_MSG_CHECKING([for a version of Python >= '2.1.0'])
+       ac_supports_python_ver=`$PYTHON -c "import sys; \
+               ver = sys.version.split ()[[0]]; \
+               print (ver >= '2.1.0')"`
+       if test "$ac_supports_python_ver" != "True"; then
+               if test -z "$PYTHON_NOVERSIONCHECK"; then
+                       AC_MSG_RESULT([no])
+                       AC_MSG_WARN([
+This version of the AC@&t@_PYTHON_DEVEL macro
+doesn't work properly with versions of Python before
+2.1.0. You may need to re-run configure, setting the
+variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG,
+PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand.
+Moreover, to disable this check, set PYTHON_NOVERSIONCHECK
+to something else than an empty string.
+])
+                        no_python_devel=yes
+               else
+                       AC_MSG_RESULT([skip at user request])
+               fi
+       else
+               AC_MSG_RESULT([yes])
+       fi
+]) # AS_IF
+
+AS_IF([test -z "$no_python_devel"], [
+       #
+       # if the macro parameter ``version'' is set, honour it
+       #
+       if test -n "$1"; then
+               AC_MSG_CHECKING([for a version of Python $1])
+               ac_supports_python_ver=`$PYTHON -c "import sys; \
+                       ver = sys.version.split ()[[0]]; \
+                       print (ver $1)"`
+               if test "$ac_supports_python_ver" = "True"; then
+                  AC_MSG_RESULT([yes])
+               else
+                       AC_MSG_RESULT([no])
+                       AC_MSG_WARN([this package requires Python $1.
+If you have it installed, but it isn't the default Python
+interpreter in your system path, please pass the PYTHON_VERSION
+variable to configure. See ``configure --help'' for reference.
+])
+                       PYTHON_VERSION=""
+                        no_python_devel=yes
+               fi
+       fi
+]) # AS_IF
+
+AS_IF([test -z "$no_python_devel"], [
+       #
+       # Check if you have distutils, else fail
+       #
+       AC_MSG_CHECKING([for the distutils Python package])
+       ac_distutils_result=`$PYTHON -c "import distutils" 2>&1`
+       if test -z "$ac_distutils_result"; then
+               AC_MSG_RESULT([yes])
+       else
+               AC_MSG_RESULT([no])
+               AC_MSG_WARN([cannot import Python module "distutils".
+Please check your Python installation. The error was:
+$ac_distutils_result])
+               PYTHON_VERSION=""
+                no_python_devel=yes
+       fi
+]) # AS_IF
+
+AS_IF([test -z "$no_python_devel"], [
+       #
+       # Check for Python include path
+       #
+       AC_MSG_CHECKING([for Python include path])
+       if test -z "$PYTHON_CPPFLAGS"; then
+               python_path=`$PYTHON -c "import distutils.sysconfig; \
+                       print (distutils.sysconfig.get_python_inc ());"`
+               plat_python_path=`$PYTHON -c "import distutils.sysconfig; \
+                       print (distutils.sysconfig.get_python_inc (plat_specific=1));"`
+               if test -n "${python_path}"; then
+                       if test "${plat_python_path}" != "${python_path}"; then
+                               python_path="-I$python_path -I$plat_python_path"
+                       else
+                               python_path="-I$python_path"
+                       fi
+               fi
+               PYTHON_CPPFLAGS=$python_path
+       fi
+       AC_MSG_RESULT([$PYTHON_CPPFLAGS])
+       AC_SUBST([PYTHON_CPPFLAGS])
+
+       #
+       # Check for Python library path
+       #
+       AC_MSG_CHECKING([for Python library path])
+       if test -z "$PYTHON_LDFLAGS"; then
+               # (makes two attempts to ensure we've got a version number
+               # from the interpreter)
+               ac_python_version=`cat<<EOD | $PYTHON -
+
+# join all versioning strings, on some systems
+# major/minor numbers could be in different list elements
+from distutils.sysconfig import *
+e = get_config_var('VERSION')
+if e is not None:
+       print(e)
+EOD`
+
+               if test -z "$ac_python_version"; then
+                       if test -n "$PYTHON_VERSION"; then
+                               ac_python_version=$PYTHON_VERSION
+                       else
+                               ac_python_version=`$PYTHON -c "import sys; \
+                                       print (sys.version[[:3]])"`
+                       fi
+               fi
+
+               # Make the versioning information available to the compiler
+               AC_DEFINE_UNQUOTED([HAVE_PYTHON], ["$ac_python_version"],
+                                   [If available, contains the Python version number currently in use.])
+
+               # First, the library directory:
+               ac_python_libdir=`cat<<EOD | $PYTHON -
+
+# There should be only one
+import distutils.sysconfig
+e = distutils.sysconfig.get_config_var('LIBDIR')
+if e is not None:
+       print (e)
+EOD`
+
+               # Now, for the library:
+               ac_python_library=`cat<<EOD | $PYTHON -
+
+import distutils.sysconfig
+c = distutils.sysconfig.get_config_vars()
+if 'LDVERSION' in c:
+       print ('python'+c[['LDVERSION']])
+else:
+       print ('python'+c[['VERSION']])
+EOD`
+
+               # This small piece shamelessly adapted from PostgreSQL python macro;
+               # credits goes to momjian, I think. I'd like to put the right name
+               # in the credits, if someone can point me in the right direction... ?
+               #
+               if test -n "$ac_python_libdir" -a -n "$ac_python_library"
+               then
+                       # use the official shared library
+                       ac_python_library=`echo "$ac_python_library" | sed "s/^lib//"`
+                       PYTHON_LDFLAGS="-L$ac_python_libdir -l$ac_python_library"
+               else
+                       # old way: use libpython from python_configdir
+                       ac_python_libdir=`$PYTHON -c \
+                         "from distutils.sysconfig import get_python_lib as f; \
+                         import os; \
+                         print (os.path.join(f(plat_specific=1, standard_lib=1), 'config'));"`
+                       PYTHON_LDFLAGS="-L$ac_python_libdir -lpython$ac_python_version"
+               fi
+
+               if test -z "PYTHON_LDFLAGS"; then
+                       AC_MSG_WARN([
+  Cannot determine location of your Python DSO. Please check it was installed with
+  dynamic libraries enabled, or try setting PYTHON_LDFLAGS by hand.
+                       ])
+                        no_python_devel=yes
+               fi
+       fi
+       AC_MSG_RESULT([$PYTHON_LDFLAGS])
+       AC_SUBST([PYTHON_LDFLAGS])
+]) # AS_IF
+
+AS_IF([test -z "$no_python_devel"], [
+       #
+       # Check for site packages
+       #
+       AC_MSG_CHECKING([for Python site-packages path])
+       if test -z "$PYTHON_SITE_PKG"; then
+               PYTHON_SITE_PKG=`$PYTHON -c "import distutils.sysconfig; \
+                       print (distutils.sysconfig.get_python_lib(0,0));"`
+       fi
+       AC_MSG_RESULT([$PYTHON_SITE_PKG])
+       AC_SUBST([PYTHON_SITE_PKG])
+
+       #
+       # libraries which must be linked in when embedding
+       #
+       AC_MSG_CHECKING(python extra libraries)
+       if test -z "$PYTHON_EXTRA_LIBS"; then
+          PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \
+                conf = distutils.sysconfig.get_config_var; \
+                print (conf('LIBS'))"`
+       fi
+       AC_MSG_RESULT([$PYTHON_EXTRA_LIBS])
+       AC_SUBST(PYTHON_EXTRA_LIBS)
+
+       #
+       # linking flags needed when embedding
+       #
+       AC_MSG_CHECKING(python extra linking flags)
+       if test -z "$PYTHON_EXTRA_LDFLAGS"; then
+               PYTHON_EXTRA_LDFLAGS=`$PYTHON -c "import distutils.sysconfig; \
+                       conf = distutils.sysconfig.get_config_var; \
+                       print (conf('LINKFORSHARED'))"`
+       fi
+       AC_MSG_RESULT([$PYTHON_EXTRA_LDFLAGS])
+       AC_SUBST(PYTHON_EXTRA_LDFLAGS)
+
+       #
+       # final check to see if everything compiles alright
+       #
+       AC_MSG_CHECKING([consistency of all components of python development environment])
+       # save current global flags
+       ac_save_LIBS="$LIBS"
+       ac_save_CPPFLAGS="$CPPFLAGS"
+       LIBS="$ac_save_LIBS $PYTHON_LDFLAGS $PYTHON_EXTRA_LDFLAGS $PYTHON_EXTRA_LIBS"
+       CPPFLAGS="$ac_save_CPPFLAGS $PYTHON_CPPFLAGS"
+       AC_LANG_PUSH([C])
+       AC_LINK_IFELSE([
+               AC_LANG_PROGRAM([[#include <Python.h>]],
+                               [[Py_Initialize();]])
+               ],[pythonexists=yes],[pythonexists=no])
+       AC_LANG_POP([C])
+       # turn back to default flags
+       CPPFLAGS="$ac_save_CPPFLAGS"
+       LIBS="$ac_save_LIBS"
+
+       AC_MSG_RESULT([$pythonexists])
+
+        if test ! "x$pythonexists" = "xyes"; then
+          AC_MSG_WARN([
+  Could not link test program to Python. Maybe the main Python library has been
+  installed in some non-standard library path. If so, pass it to configure,
+  via the LDFLAGS environment variable.
+  Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib"
+  ============================================================================
+   ERROR!
+   You probably have to install the development version of the Python package
+   for your distribution.  The exact name of this package varies among them.
+  ============================================================================
+          ])
+         PYTHON_VERSION=""
+          no_python_devel=yes
+       fi
+
+       #
+       # all done!
+       #
+]) # AS_IF
+
+AS_IF([test -z "$no_python_devel"],
+      [have_python_dev=yes], [have_python_dev=no])
+
+]) # AS_IF
diff --git a/makemanpages b/makemanpages
new file mode 100755 (executable)
index 0000000..0b7c54e
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/sh -e
+
+for prog in nghttp nghttpd nghttpx h2load; do
+    src/$prog -h | ./help2rst.py -i doc/$prog.h2r > doc/$prog.1.rst
+done
+
+cd doc
+make man
+
+for prog in nghttp nghttpd nghttpx h2load; do
+    cp manual/man/$prog.1 $prog.1
+done
diff --git a/makerelease.sh b/makerelease.sh
new file mode 100755 (executable)
index 0000000..054f6a6
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh -e
+
+TAG=$1
+PREV_TAG=$2
+
+git checkout refs/tags/$TAG
+git log --pretty=fuller --date=short refs/tags/$PREV_TAG..HEAD > ChangeLog
+
+./configure && \
+    make dist-bzip2 && make dist-gzip && make dist-xz || echo "error"
+make distclean
diff --git a/mkcipherlist.py b/mkcipherlist.py
new file mode 100755 (executable)
index 0000000..95c6288
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This script read cipher suite list csv file [1] and prints out ECDHE
+# or DHE with AEAD ciphers only.  The output is used by
+# src/shrpx_ssl.cc.
+#
+# [1] http://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv
+# [2] http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
+
+from __future__ import unicode_literals
+import re
+import sys
+import csv
+
+pat = re.compile(r'\ATLS_(?:ECDHE|DHE)_.*_GCM')
+
+ciphers = []
+for hl, name, _, _ in csv.reader(sys.stdin):
+    if not pat.match(name):
+        continue
+
+    high, low = hl.split(',')
+
+    id = high + low[2:] + 'u'
+    ciphers.append((id, name))
+
+print '''\
+enum {'''
+
+for id, name in ciphers:
+    print '{} = {},'.format(name, id)
+
+print '''\
+};
+'''
+
+for id, name in ciphers:
+    print '''\
+case {}:'''.format(name)
diff --git a/mkhufftbl.py b/mkhufftbl.py
new file mode 100755 (executable)
index 0000000..c243abc
--- /dev/null
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This script reads Huffman Code table [1] and generates symbol table
+# and decoding tables in C language.  The resulting code is used in
+# lib/nghttp2_hd_huffman.h and lib/nghttp2_hd_huffman_data.c
+#
+# [1] http://http2.github.io/http2-spec/compression.html
+
+from __future__ import unicode_literals
+import re
+import sys
+
+class Node:
+    def __init__(self, term = None):
+        self.term = term
+        self.left = None
+        self.right = None
+        self.trans = []
+        self.id = None
+        self.accept = False
+
+def to_bin(s):
+    res = []
+    for i in range(0, len(s), 8):
+        x = s[i:i+8]
+        x += '0'*(8 - len(x))
+        a = 0
+        for j in range(8):
+            a *= 2
+            a += ord(x[j]) - ord('0')
+        res.append(a) #chr(a))
+    return res
+
+nodes = []
+
+def insert(node, sym, bits):
+    if len(bits) == 0:
+        node.term = sym
+        return
+    else:
+        if bits[0] == '0':
+            if node.left is None:
+                node.left = Node()
+            child = node.left
+        else:
+            if node.right is None:
+                node.right = Node()
+            child = node.right
+        insert(child, sym, bits[1:])
+
+def traverse(node, bits, syms, start_node, root, depth):
+    if depth == 4:
+        if 256 in syms:
+            syms = []
+            node = None
+        start_node.trans.append((node, bits, syms))
+        return
+
+    if node.term is not None:
+        node = root
+
+    def go(node, bit):
+        nbits = list(bits)
+        nbits.append(bit)
+        nsyms = list(syms)
+        if node.term is not None:
+            nsyms.append(node.term)
+        traverse(node, nbits, nsyms, start_node, root, depth + 1)
+
+    go(node.left, 0)
+    go(node.right, 1)
+
+idseed = 0
+
+def dfs_setid(node, prefix):
+    if node.term is not None:
+        return
+    if len(prefix) <= 7 and [1] * len(prefix) == prefix:
+        node.accept = True
+    global idseed
+    node.id = idseed
+    idseed += 1
+    dfs_setid(node.left, prefix + [0])
+    dfs_setid(node.right, prefix + [1])
+
+def dfs(node, root):
+    if node is None:
+        return
+    traverse(node, [], [], node, root, 0)
+    dfs(node.left, root)
+    dfs(node.right, root)
+
+NGHTTP2_HUFF_ACCEPTED = 1
+NGHTTP2_HUFF_SYM = 1 << 1
+NGHTTP2_HUFF_FAIL = 1 << 2
+
+def dfs_print(node):
+    if node.term is not None:
+        return
+    print '/* {} */'.format(node.id)
+    print '{'
+    for nd, bits, syms in node.trans:
+        outlen = len(syms)
+        flags = 0
+        if outlen == 0:
+            out = 0
+        else:
+            assert(outlen == 1)
+            out = syms[0]
+            flags |= NGHTTP2_HUFF_SYM
+        if nd is None:
+            id = 0
+            flags |= NGHTTP2_HUFF_FAIL
+        else:
+            id = nd.id
+            if id is None:
+                # if nd.id is None, it is a leaf node
+                id = 0
+                flags |= NGHTTP2_HUFF_ACCEPTED
+            elif nd.accept:
+                flags |= NGHTTP2_HUFF_ACCEPTED
+        print '  {{{}, 0x{:02x}, {}}},'.format(id, flags, out)
+    print '},'
+    dfs_print(node.left)
+    dfs_print(node.right)
+
+symbol_tbl = [(None, 0) for i in range(257)]
+tables = {}
+
+root = Node()
+
+for line in sys.stdin:
+    m = re.match(r'.*\(\s*(\d+)\)\s+([|01]+)\s+(\S+)\s+\[\s*(\d+)\].*', line)
+    if m:
+        #print m.group(1), m.group(2), m.group(4)
+        if len(m.group(3)) > 8:
+            raise Error('Code is more than 4 bytes long')
+        sym = int(m.group(1))
+        bits = re.sub(r'\|', '', m.group(2))
+        nbits = int(m.group(4))
+        assert(len(bits) == nbits)
+        binpat = to_bin(bits)
+        assert(len(binpat) == (nbits+7)/8)
+        symbol_tbl[sym] = (binpat, nbits, m.group(3))
+        #print "Inserting", sym
+        insert(root, sym, bits)
+
+dfs_setid(root, [])
+dfs(root, root)
+
+print '''\
+typedef struct {
+  uint32_t nbits;
+  uint32_t code;
+} nghttp2_huff_sym;
+'''
+
+print '''\
+const nghttp2_huff_sym huff_sym_table[] = {'''
+for i in range(257):
+    pat = list(symbol_tbl[i][0])
+    pat += [0]*(4 - len(pat))
+    print '''\
+  {{ {}, 0x{}u }}{}\
+'''.format(symbol_tbl[i][1], symbol_tbl[i][2], ',' if i < 256 else '')
+print '};'
+print ''
+
+print '''\
+enum {{
+  NGHTTP2_HUFF_ACCEPTED = {},
+  NGHTTP2_HUFF_SYM = {},
+  NGHTTP2_HUFF_FAIL = {},
+}} nghttp2_huff_decode_flag;
+'''.format(NGHTTP2_HUFF_ACCEPTED, NGHTTP2_HUFF_SYM, NGHTTP2_HUFF_FAIL)
+
+print '''\
+typedef struct {
+  uint8_t state;
+  uint8_t flags;
+  uint8_t sym;
+} nghttp2_huff_decode;
+'''
+
+print '''\
+const nghttp2_huff_decode huff_decode_table[][16] = {'''
+dfs_print(root)
+print '};'
diff --git a/mkstatichdtbl.py b/mkstatichdtbl.py
new file mode 100755 (executable)
index 0000000..b6b6e38
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# This scripts reads static table entries [1] and generates
+# nghttp2_hd_static_entry table.  This table is used in
+# lib/nghttp2_hd.c.
+#
+# [1] http://http2.github.io/http2-spec/compression.html
+
+from __future__ import unicode_literals
+import re, sys
+
+def hash(s):
+    h = 0
+    for c in s:
+        h = h * 31 + ord(c)
+    return h & ((1 << 32) - 1)
+
+entries = []
+for line in sys.stdin:
+    m = re.match(r'(\d+)\s+(\S+)\s+(\S.*)?', line)
+    val = m.group(3).strip() if m.group(3) else ''
+    entries.append((hash(m.group(2)), int(m.group(1)), m.group(2), val))
+
+entries.sort()
+
+print '/* Sorted by hash(name) and its table index */'
+print 'static nghttp2_hd_static_entry static_table[] = {'
+for ent in entries:
+    print 'MAKE_STATIC_ENT({}, "{}", "{}", {}u, {}u),'\
+        .format(ent[1] - 1, ent[2], ent[3], ent[0], hash(ent[3]))
+print '};'
+
+print ''
+
+print '/* Index to the position in static_table */'
+print 'const size_t static_table_index[] = {'
+for i in range(len(entries)):
+    for j, ent in enumerate(entries):
+        if ent[1] - 1 == i:
+            sys.stdout.write('{: <2d},'.format(j))
+            break
+    if (i + 1) % 16 == 0:
+        sys.stdout.write('\n')
+    else:
+        sys.stdout.write(' ')
+
+print '};'
diff --git a/nghttpx.conf.sample b/nghttpx.conf.sample
new file mode 100644 (file)
index 0000000..a2132e5
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# Sample configuration file for nghttpx.
+#
+# * Line staring '#' is treated as comment.
+#
+# * The option name in the configuration file is the long command-line
+#   option name with leading '--' stripped (e.g., frontend). Put '='
+#   between option name and value. Don't put extra leading or trailing
+#   spaces.
+#
+# * The options which do not take argument in the command-line *take*
+#   argument in the configuration file. Specify 'yes' as argument
+#   (e.g., spdy-proxy=yes). If other string is given, it disables the
+#   option.
+#
+# * To specify private key and certificate file, use private-key-file
+#   and certificate-file. See the examples below.
+#
+# * conf option cannot be used in the configuration file. It will be
+#   ignored.
+#
+# Examples:
+#
+# frontend=0.0.0.0,3000
+# backend=127.0.0.1,80
+# private-key-file=/path/to/server.key
+# certificate-file=/path/to/server.crt
+# spdy-proxy=no
+# workers=1
diff --git a/packaging/nghttp2.spec b/packaging/nghttp2.spec
new file mode 100644 (file)
index 0000000..2670a60
--- /dev/null
@@ -0,0 +1,64 @@
+%define _unpackaged_files_terminate_build 0
+Prefix: %{_usr}
+Name: nghttp2
+Version: 0.7.3
+Release: 1
+Summary: This is an experimental implementation of Hypertext Transfer Protocol version 2.
+Group: System Environment/Libraries
+License: MIT
+URL: https://nghttp2.org/
+Source0: %{name}-%{version}.tar.gz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+BuildRequires: pkgconfig >= 0.20, zlib >= 1.2.3, gcc, gcc-c++, make
+BuildRequires: openssl-devel
+BuildRequires: libxml2-devel
+
+%description
+
+%package devel
+Summary: Development files for %{name}
+Group: Development/Libraries
+Requires: %{name} = %{version}-%{release}
+
+%description devel
+The %{name}-devel package contains libraries and header files for
+developing applications that use %{name}.
+
+%prep
+%setup -q
+
+%build
+autoreconf -i
+%{__automake}
+%{__autoconf}
+%configure --disable-static --disable-xmltest
+%{__make} %{?_smp_mflags}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/usr/share/license
+cp COPYING $RPM_BUILD_ROOT/usr/share/license/%{name}
+%{__make} install DESTDIR=$RPM_BUILD_ROOT
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post -p /sbin/ldconfig
+
+%postun -p /sbin/ldconfig
+
+%files
+%defattr(-,root,root,-)
+%doc
+%{_libdir}/*.so*
+%exclude %{_libdir}/*.la
+%{_includedir}/*
+/usr/share/license/%{name}
+
+%files devel
+%defattr(-,root,root,-)
+%doc %{_docdir}/%{name}
+%{_includedir}/*
+%{_libdir}/*.so
+%{_libdir}/pkgconfig/*.pc
diff --git a/pre-commit b/pre-commit
new file mode 100755 (executable)
index 0000000..1552c61
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh -e
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments.  The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+
+CLANGFORMATDIFF=`git config --get clangformatdiff.binary`
+
+if [ -z "$CLANGFORMATDIFF" ]; then
+    CLANGFORMATDIFF=clang-format-diff.py
+fi
+
+errors=`git diff-index --cached --diff-filter=ACMR -p HEAD -- | $CLANGFORMATDIFF -p1`
+
+if [ -n "$errors" ]; then
+    echo "$errors"
+    echo "--"
+    echo "[ERROR] We have detected the difference between the code to commit"
+    echo "and clang-format style rules.  Please fix this problem in either:"
+    echo "1) Apply patch above."
+    echo "2) Use clang-format to format lines."
+    echo "3) Reformat these lines manually."
+    echo "Aborting commit."
+    exit 1
+fi
diff --git a/proxy.pac.sample b/proxy.pac.sample
new file mode 100644 (file)
index 0000000..9283920
--- /dev/null
@@ -0,0 +1,6 @@
+function FindProxyForURL(url, host) {
+  // For SPDY proxy
+  return "HTTPS localhost:3000";
+  // For conventional HTTP proxy
+  // return "PROXY localhost:3000";
+}
diff --git a/python/Makefile.am b/python/Makefile.am
new file mode 100644 (file)
index 0000000..911888f
--- /dev/null
@@ -0,0 +1,48 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This will avoid that setup.py gets deleted before it is executed in
+# clean-local in parallel build.
+.NOTPARALLEL:
+
+EXTRA_DIST = cnghttp2.pxd nghttp2.pyx
+
+if ENABLE_PYTHON_BINDINGS
+
+all-local: nghttp2.c
+       $(PYTHON) setup.py build
+
+install-exec-local:
+       $(PYTHON) setup.py install --prefix=$(DESTDIR)$(prefix)
+
+uninstall-local:
+       rm -rf $(DESTDIR)$(libdir)/python*/site-packages/*nghttp2*
+
+clean-local:
+       $(PYTHON) setup.py clean --all
+       -rm -f $(builddir)/nghttp2.c
+
+.pyx.c:
+       $(CYTHON) -o $@ $<
+
+endif # ENABLE_PYTHON_BINDINGS
diff --git a/python/calcratio.py b/python/calcratio.py
new file mode 100755 (executable)
index 0000000..0c52a16
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+#
+# This script takes directories which contain the hpack-test-case json
+# files, and calculates the compression ratio in each file and outputs
+# the result in table formatted in rst.
+#
+# The each directory contains the result of various HPACK compressor.
+#
+# The table is laid out so that we can see that how input header set
+# in one json file is compressed in each compressor.
+#
+# For hpack-test-case, see https://github.com/Jxck/hpack-test-case
+#
+import sys, json, os, re
+
+class Stat:
+
+    def __init__(self, complen, srclen):
+        self.complen = complen
+        self.srclen = srclen
+
+def compute_stat(jsdata):
+    complen = 0
+    srclen = 0
+    for item in jsdata['cases']:
+        complen += len(item['wire']) // 2
+        srclen += \
+                  sum([len(list(x.keys())[0]) + len(list(x.values())[0]) \
+                       for x in item['headers']])
+    return Stat(complen, srclen)
+
+def format_result(r):
+    return '{:.02f} ({}/{}) '.format(r.complen/r.srclen, r.complen, r.srclen)
+
+if __name__ == '__main__':
+    entries = [(os.path.basename(re.sub(r'/+$', '', p)), p) \
+               for p in sys.argv[1:]]
+    maxnamelen = 0
+    maxstorynamelen = 0
+    res = {}
+
+    stories = set()
+    for name, ent in entries:
+        files = [p for p in os.listdir(ent) if p.endswith('.json')]
+        res[name] = {}
+        maxnamelen = max(maxnamelen, len(name))
+        for fn in files:
+            stories.add(fn)
+            maxstorynamelen = max(maxstorynamelen, len(fn))
+            with open(os.path.join(ent, fn)) as f:
+                input = f.read()
+            rv = compute_stat(json.loads(input))
+            res[name][fn] = rv
+            maxnamelen = max(maxnamelen, len(format_result(rv)))
+    stories = list(stories)
+    stories.sort()
+
+    storynameformat = '{{:{}}} '.format(maxstorynamelen)
+    nameformat = '{{:{}}} '.format(maxnamelen)
+
+
+    sys.stdout.write('''\
+hpack-test-case compression ratio
+=================================
+
+The each cell has ``X (Y/Z)`` format:
+
+X
+  Y / Z
+Y
+  number of bytes after compression
+Z
+  number of bytes before compression
+
+''')
+
+    def write_border():
+        sys.stdout.write('='*maxstorynamelen)
+        sys.stdout.write(' ')
+        for _ in entries:
+            sys.stdout.write('='*maxnamelen)
+            sys.stdout.write(' ')
+        sys.stdout.write('\n')
+
+    write_border()
+
+    sys.stdout.write(storynameformat.format('story'))
+    for name, _ in entries:
+        sys.stdout.write(nameformat.format(name))
+    sys.stdout.write('\n')
+
+    write_border()
+
+    for story in stories:
+        sys.stdout.write(storynameformat.format(story))
+        srclen = -1
+        for name, _ in entries:
+            stats = res[name]
+            if story not in stats:
+                sys.stdout.write(nameformat.format('N/A'))
+                continue
+            if srclen == -1:
+                srclen = stats[story].srclen
+            elif srclen != stats[story].srclen:
+                raise Exception('Bad srclen')
+            sys.stdout.write(nameformat.format(format_result(stats[story])))
+        sys.stdout.write('\n')
+
+    write_border()
diff --git a/python/cnghttp2.pxd b/python/cnghttp2.pxd
new file mode 100644 (file)
index 0000000..a5d2325
--- /dev/null
@@ -0,0 +1,341 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t
+
+cdef extern from 'nghttp2/nghttp2.h':
+
+    const char NGHTTP2_PROTO_VERSION_ID[]
+    const char NGHTTP2_CLIENT_CONNECTION_PREFACE[]
+    const size_t NGHTTP2_INITIAL_WINDOW_SIZE
+    const size_t NGHTTP2_DEFAULT_HEADER_TABLE_SIZE
+
+    ctypedef struct nghttp2_session:
+        pass
+
+    ctypedef enum nghttp2_error:
+        NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE
+
+    ctypedef enum nghttp2_flag:
+        NGHTTP2_FLAG_NONE
+        NGHTTP2_FLAG_END_STREAM
+        NGHTTP2_FLAG_ACK
+
+    ctypedef enum nghttp2_error_code:
+        NGHTTP2_NO_ERROR
+        NGHTTP2_PROTOCOL_ERROR
+        NGHTTP2_INTERNAL_ERROR
+        NGHTTP2_SETTINGS_TIMEOUT
+
+    ctypedef enum nghttp2_frame_type:
+        NGHTTP2_DATA
+        NGHTTP2_HEADERS
+        NGHTTP2_RST_STREAM
+        NGHTTP2_SETTINGS
+        NGHTTP2_PUSH_PROMISE
+        NGHTTP2_GOAWAY
+
+    ctypedef enum nghttp2_nv_flag:
+        NGHTTP2_NV_FLAG_NONE
+        NGHTTP2_NV_FLAG_NO_INDEX
+
+    ctypedef struct nghttp2_nv:
+        uint8_t *name
+        uint8_t *value
+        uint16_t namelen
+        uint16_t valuelen
+        uint8_t flags
+
+    ctypedef enum nghttp2_settings_id:
+        SETTINGS_HEADER_TABLE_SIZE
+        NGHTTP2_SETTINGS_HEADER_TABLE_SIZE
+        NGHTTP2_SETTINGS_ENABLE_PUSH
+        NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS
+        NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE
+
+    ctypedef struct nghttp2_settings_entry:
+        int32_t settings_id
+        uint32_t value
+
+    ctypedef struct nghttp2_frame_hd:
+        size_t length
+        int32_t stream_id
+        uint8_t type
+        uint8_t flags
+
+    ctypedef struct nghttp2_data:
+        nghttp2_frame_hd hd
+        size_t padlen
+
+    ctypedef enum nghttp2_headers_category:
+        NGHTTP2_HCAT_REQUEST
+        NGHTTP2_HCAT_RESPONSE
+        NGHTTP2_HCAT_PUSH_RESPONSE
+        NGHTTP2_HCAT_HEADERS
+
+    ctypedef struct nghttp2_headers:
+        nghttp2_frame_hd hd
+        size_t padlen
+        nghttp2_nv *nva
+        size_t nvlen
+        nghttp2_headers_category cat
+        int32_t pri
+
+    ctypedef struct nghttp2_rst_stream:
+        nghttp2_frame_hd hd
+        uint32_t error_code
+
+
+    ctypedef struct nghttp2_push_promise:
+        nghttp2_frame_hd hd
+        nghttp2_nv *nva
+        size_t nvlen
+        int32_t promised_stream_id
+
+    ctypedef struct nghttp2_goaway:
+        nghttp2_frame_hd hd
+        int32_t last_stream_id
+        uint32_t error_code
+        uint8_t *opaque_data
+        size_t opaque_data_len
+
+    ctypedef union nghttp2_frame:
+        nghttp2_frame_hd hd
+        nghttp2_data data
+        nghttp2_headers headers
+        nghttp2_rst_stream rst_stream
+        nghttp2_push_promise push_promise
+        nghttp2_goaway goaway
+
+    ctypedef ssize_t (*nghttp2_send_callback)\
+        (nghttp2_session *session, const uint8_t *data, size_t length,
+         int flags, void *user_data)
+
+    ctypedef int (*nghttp2_on_frame_recv_callback)\
+        (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+
+    ctypedef int (*nghttp2_on_data_chunk_recv_callback)\
+        (nghttp2_session *session, uint8_t flags, int32_t stream_id,
+         const uint8_t *data, size_t length, void *user_data)
+
+    ctypedef int (*nghttp2_before_frame_send_callback)\
+        (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+
+    ctypedef int (*nghttp2_on_stream_close_callback)\
+        (nghttp2_session *session, int32_t stream_id,
+         uint32_t error_code, void *user_data)
+
+    ctypedef int (*nghttp2_on_begin_headers_callback)\
+        (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+
+    ctypedef int (*nghttp2_on_header_callback)\
+        (nghttp2_session *session,
+         const nghttp2_frame *frame,
+         const uint8_t *name, size_t namelen,
+         const uint8_t *value, size_t valuelen,
+         uint8_t flags,
+         void *user_data)
+
+    ctypedef int (*nghttp2_on_frame_send_callback)\
+        (nghttp2_session *session, const nghttp2_frame *frame, void *user_data)
+
+    ctypedef int (*nghttp2_on_frame_not_send_callback)\
+        (nghttp2_session *session, const nghttp2_frame *frame,
+         int lib_error_code, void *user_data)
+
+    ctypedef struct nghttp2_session_callbacks:
+        pass
+
+    int nghttp2_session_callbacks_new(
+        nghttp2_session_callbacks **callbacks_ptr)
+
+    void nghttp2_session_callbacks_del(nghttp2_session_callbacks *callbacks)
+
+    void nghttp2_session_callbacks_set_send_callback(
+        nghttp2_session_callbacks *cbs, nghttp2_send_callback send_callback)
+
+    void nghttp2_session_callbacks_set_on_frame_recv_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_frame_recv_callback on_frame_recv_callback)
+
+    void nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_data_chunk_recv_callback on_data_chunk_recv_callback)
+
+    void nghttp2_session_callbacks_set_before_frame_send_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_before_frame_send_callback before_frame_send_callback)
+
+    void nghttp2_session_callbacks_set_on_frame_send_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_frame_send_callback on_frame_send_callback)
+
+    void nghttp2_session_callbacks_set_on_frame_not_send_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_frame_not_send_callback on_frame_not_send_callback)
+
+    void nghttp2_session_callbacks_set_on_stream_close_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_stream_close_callback on_stream_close_callback)
+
+    void nghttp2_session_callbacks_set_on_begin_headers_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_begin_headers_callback on_begin_headers_callback)
+
+    void nghttp2_session_callbacks_set_on_header_callback(
+        nghttp2_session_callbacks *cbs,
+        nghttp2_on_header_callback on_header_callback)
+
+    int nghttp2_session_client_new(nghttp2_session **session_ptr,
+                                   const nghttp2_session_callbacks *callbacks,
+                                   void *user_data)
+
+    int nghttp2_session_server_new(nghttp2_session **session_ptr,
+                                   const nghttp2_session_callbacks *callbacks,
+                                   void *user_data)
+
+    void nghttp2_session_del(nghttp2_session *session)
+
+
+    ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
+                                     const uint8_t *data, size_t datalen)
+
+    ssize_t nghttp2_session_mem_send(nghttp2_session *session,
+                                     const uint8_t **data_ptr)
+
+    int nghttp2_session_send(nghttp2_session *session)
+
+    int nghttp2_session_want_read(nghttp2_session *session)
+
+    int nghttp2_session_want_write(nghttp2_session *session)
+
+    ctypedef union nghttp2_data_source:
+        int fd
+        void *ptr
+
+    ctypedef enum nghttp2_data_flag:
+        NGHTTP2_DATA_FLAG_NONE
+        NGHTTP2_DATA_FLAG_EOF
+
+    ctypedef ssize_t (*nghttp2_data_source_read_callback)\
+        (nghttp2_session *session, int32_t stream_id,
+         uint8_t *buf, size_t length, uint32_t *data_flags,
+         nghttp2_data_source *source, void *user_data)
+
+    ctypedef struct nghttp2_data_provider:
+        nghttp2_data_source source
+        nghttp2_data_source_read_callback read_callback
+
+    ctypedef struct nghttp2_priority_spec:
+        int32_t stream_id
+        int32_t weight
+        uint8_t exclusive
+
+    int nghttp2_submit_request(nghttp2_session *session, const nghttp2_priority_spec *pri_spec,
+                               const nghttp2_nv *nva, size_t nvlen,
+                               const nghttp2_data_provider *data_prd,
+                               void *stream_user_data)
+
+    int nghttp2_submit_response(nghttp2_session *session,
+                                int32_t stream_id,
+                                const nghttp2_nv *nva, size_t nvlen,
+                                const nghttp2_data_provider *data_prd)
+
+    int nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags,
+                                    int32_t stream_id,
+                                    const nghttp2_nv *nva, size_t nvlen,
+                                    void *stream_user_data)
+
+    int nghttp2_submit_settings(nghttp2_session *session, uint8_t flags,
+                                const nghttp2_settings_entry *iv, size_t niv)
+
+    int nghttp2_submit_rst_stream(nghttp2_session *session, uint8_t flags,
+                                  int32_t stream_id,
+                                  uint32_t error_code)
+
+    void* nghttp2_session_get_stream_user_data(nghttp2_session *session,
+                                               uint32_t stream_id)
+
+    int nghttp2_session_set_stream_user_data(nghttp2_session *session,
+                                             uint32_t stream_id,
+                                             void *stream_user_data)
+
+    int nghttp2_session_terminate_session(nghttp2_session *session,
+                                          uint32_t error_code)
+
+    const char* nghttp2_strerror(int lib_error_code)
+
+    int nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr,
+                               size_t deflate_hd_table_bufsize_max)
+
+    void nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater)
+
+    int nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater,
+                                             size_t hd_table_bufsize_max)
+
+    ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater,
+                                  uint8_t *buf, size_t buflen,
+                                  const nghttp2_nv *nva, size_t nvlen)
+
+    size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater,
+                                    const nghttp2_nv *nva, size_t nvlen)
+
+    int nghttp2_hd_inflate_new(nghttp2_hd_inflater **inflater_ptr)
+
+    void nghttp2_hd_inflate_del(nghttp2_hd_inflater *inflater)
+
+    int nghttp2_hd_inflate_change_table_size(nghttp2_hd_inflater *inflater,
+                                             size_t hd_table_bufsize_max)
+
+    ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_inflater *inflater,
+                                  nghttp2_nv *nv_out, int *inflate_flags,
+                                  uint8_t *input, size_t inlen, int in_final)
+
+    int nghttp2_hd_inflate_end_headers(nghttp2_hd_inflater *inflater)
+
+cdef extern from 'nghttp2_hd.h':
+
+    # This is macro
+    int NGHTTP2_HD_ENTRY_OVERHEAD
+
+    ctypedef enum nghttp2_hd_inflate_flag:
+        NGHTTP2_HD_INFLATE_EMIT
+        NGHTTP2_HD_INFLATE_FINAL
+
+    ctypedef struct nghttp2_hd_entry:
+        nghttp2_nv nv
+        uint8_t flags
+
+    ctypedef struct nghttp2_hd_ringbuf:
+        size_t len
+
+    ctypedef struct nghttp2_hd_context:
+        nghttp2_hd_ringbuf hd_table
+
+    ctypedef struct nghttp2_hd_deflater:
+        nghttp2_hd_context ctx
+
+    ctypedef struct nghttp2_hd_inflater:
+        nghttp2_hd_context ctx
+
+    nghttp2_hd_entry* nghttp2_hd_table_get(nghttp2_hd_context *context,
+                                           size_t index)
diff --git a/python/hpackcheck.py b/python/hpackcheck.py
new file mode 100755 (executable)
index 0000000..88cb350
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+#
+# This script reads json files given in the command-line (each file
+# must be written in the format described in
+# https://github.com/Jxck/hpack-test-case). And then it decompresses
+# the sequence of encoded header blocks (which is the value of 'wire'
+# key) and checks that decompressed header set is equal to the input
+# header set (which is the value of 'headers' key). If there is
+# mismatch, exception will be raised.
+#
+import sys, json
+from binascii import a2b_hex
+import nghttp2
+
+def testsuite(testdata):
+    inflater = nghttp2.HDInflater()
+
+    for casenum, item  in enumerate(testdata['cases']):
+        if 'header_table_size' in item:
+            hd_table_size = int(item['header_table_size'])
+            inflater.change_table_size(hd_table_size)
+        compressed = a2b_hex(item['wire'])
+        # sys.stderr.write('#{} WIRE:\n{}\n'.format(casenum+1, item['wire']))
+        # TODO decompressed headers are not necessarily UTF-8 strings
+        hdrs = [(k.decode('utf-8'), v.decode('utf-8')) \
+                for k, v in inflater.inflate(compressed)]
+
+        expected_hdrs = [(list(x.keys())[0],
+                          list(x.values())[0]) for x in item['headers']]
+        if hdrs != expected_hdrs:
+            if 'seqno' in item:
+                seqno = item['seqno']
+            else:
+                seqno = casenum
+
+            sys.stderr.write('FAIL seqno#{}\n'.format(seqno))
+            sys.stderr.write('expected:\n')
+            for k, v in expected_hdrs:
+                sys.stderr.write('{}: {}\n'.format(k, v))
+            sys.stderr.write(', but got:\n')
+            for k, v in hdrs:
+                sys.stderr.write('{}: {}\n'.format(k, v))
+            raise Exception('test failure')
+    sys.stderr.write('PASS\n')
+
+if __name__ == '__main__':
+    for filename in sys.argv[1:]:
+        sys.stderr.write('{}: '.format(filename))
+        with open(filename) as f:
+            input = f.read()
+
+        testdata = json.loads(input)
+
+        if 'draft' not in testdata or testdata['draft'] != 9:
+            sys.stderr.write('Not supported\n')
+            continue
+
+        testsuite(json.loads(input))
diff --git a/python/hpackmake.py b/python/hpackmake.py
new file mode 100755 (executable)
index 0000000..4ceea5c
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+#
+# This script reads input headers from json file given in the
+# command-line (each file must be written in the format described in
+# https://github.com/Jxck/hpack-test-case but we require only
+# 'headers' data). Then it encodes input header set and write the
+# encoded header block in the same format. The output files are
+# created under 'out' directory in the current directory. It must
+# exist, otherwise the script will fail. The output filename is the
+# same as the input filename.
+#
+import sys, base64, json, os.path, os, argparse, errno
+from binascii import b2a_hex
+import nghttp2
+
+def testsuite(testdata, filename, outdir, table_size, deflate_table_size,
+              simulate_table_size_change):
+    res = {
+        'draft':9,
+        'description': '''\
+Encoded by nghttp2. The basic encoding strategy is described in \
+http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html \
+We use huffman encoding only if it produces strictly shorter byte string than \
+original. We make some headers not indexing at all, but this does not always \
+result in less bits on the wire.'''
+    }
+    cases = []
+    deflater = nghttp2.HDDeflater(deflate_table_size)
+
+    if table_size != nghttp2.DEFAULT_HEADER_TABLE_SIZE:
+        deflater.change_table_size(table_size)
+
+    num_item = len(testdata['cases'])
+
+    change_points = {}
+    if simulate_table_size_change and num_item > 1:
+        change_points[num_item * 2 // 3] = table_size * 2 // 3
+        change_points[num_item // 3] = table_size // 3
+
+    for casenum, item  in enumerate(testdata['cases']):
+        outitem = {
+            'seqno': casenum,
+            'headers': item['headers']
+        }
+
+        if casenum in change_points:
+            new_table_size = change_points[casenum]
+            deflater.change_table_size(new_table_size)
+            outitem['header_table_size'] = new_table_size
+
+        casenum += 1
+        hdrs = [(list(x.keys())[0].encode('utf-8'),
+                 list(x.values())[0].encode('utf-8')) \
+                for x in item['headers']]
+        outitem['wire'] = b2a_hex(deflater.deflate(hdrs)).decode('utf-8')
+        cases.append(outitem)
+
+    if cases and table_size != nghttp2.DEFAULT_HEADER_TABLE_SIZE:
+        cases[0]['header_table_size'] = table_size
+
+    res['cases'] = cases
+    jsonstr = json.dumps(res, indent=2)
+    with open(os.path.join(outdir, filename), 'w') as f:
+        f.write(jsonstr)
+
+if __name__ == '__main__':
+    ap = argparse.ArgumentParser(description='HPACK test case generator')
+    ap.add_argument('-d', '--dir', help='output directory', default='out')
+    ap.add_argument('-s', '--table-size', help='max header table size',
+                    type=int, default=nghttp2.DEFAULT_HEADER_TABLE_SIZE)
+    ap.add_argument('-S', '--deflate-table-size',
+                    help='max header table size for deflater',
+                    type=int, default=nghttp2.DEFLATE_MAX_HEADER_TABLE_SIZE)
+    ap.add_argument('-c', '--simulate-table-size-change',
+                    help='simulate table size change scenario',
+                    action='store_true')
+
+    ap.add_argument('file', nargs='*', help='input file')
+    args = ap.parse_args()
+    try:
+        os.mkdir(args.dir)
+    except OSError as e:
+        if e.errno != errno.EEXIST:
+            raise e
+    for filename in args.file:
+        sys.stderr.write('{}\n'.format(filename))
+        with open(filename) as f:
+            input = f.read()
+        testsuite(json.loads(input), os.path.basename(filename),
+                  args.dir, args.table_size, args.deflate_table_size,
+                  args.simulate_table_size_change)
diff --git a/python/nghttp2.pyx b/python/nghttp2.pyx
new file mode 100644 (file)
index 0000000..594f44a
--- /dev/null
@@ -0,0 +1,1579 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+cimport cnghttp2
+
+from libc.stdlib cimport malloc, free
+from libc.string cimport memcpy, memset
+from libc.stdint cimport uint8_t, uint16_t, uint32_t, int32_t
+import logging
+
+
+DEFAULT_HEADER_TABLE_SIZE = cnghttp2.NGHTTP2_DEFAULT_HEADER_TABLE_SIZE
+DEFLATE_MAX_HEADER_TABLE_SIZE = 4096
+
+HD_ENTRY_OVERHEAD = cnghttp2.NGHTTP2_HD_ENTRY_OVERHEAD
+
+class HDTableEntry:
+
+    def __init__(self, name, namelen, value, valuelen):
+        self.name = name
+        self.namelen = namelen
+        self.value = value
+        self.valuelen = valuelen
+
+    def space(self):
+        return self.namelen + self.valuelen + HD_ENTRY_OVERHEAD
+
+cdef _get_hd_table(cnghttp2.nghttp2_hd_context *ctx):
+    cdef int length = ctx.hd_table.len
+    cdef cnghttp2.nghttp2_hd_entry *entry
+    res = []
+    for i in range(length):
+        entry = cnghttp2.nghttp2_hd_table_get(ctx, i)
+        k = _get_pybytes(entry.nv.name, entry.nv.namelen)
+        v = _get_pybytes(entry.nv.value, entry.nv.valuelen)
+        res.append(HDTableEntry(k, entry.nv.namelen,
+                                v, entry.nv.valuelen))
+    return res
+
+cdef _get_pybytes(uint8_t *b, uint16_t blen):
+    return b[:blen]
+
+cdef class HDDeflater:
+    '''Performs header compression. The constructor takes
+    |hd_table_bufsize_max| parameter, which limits the usage of header
+    table in the given amount of bytes. This is necessary because the
+    header compressor and decompressor share the same amount of
+    header table and the decompressor decides that number. The
+    compressor may not want to use all header table size because of
+    limited memory availability. In that case, the
+    |hd_table_bufsize_max| can be used to cap the upper limit of table
+    size whatever the header table size is chosen by the decompressor.
+    The default value of |hd_table_bufsize_max| is 4096 bytes.
+
+    The following example shows how to compress request header sets:
+
+        import binascii, nghttp2
+
+        deflater = nghttp2.HDDeflater()
+        res = deflater.deflate([(b'foo', b'bar'),
+                              (b'baz', b'buz')])
+        print(binascii.b2a_hex(res))
+
+    '''
+
+    cdef cnghttp2.nghttp2_hd_deflater *_deflater
+
+    def __cinit__(self, hd_table_bufsize_max = DEFLATE_MAX_HEADER_TABLE_SIZE):
+        rv = cnghttp2.nghttp2_hd_deflate_new(&self._deflater,
+                                             hd_table_bufsize_max)
+        if rv != 0:
+            raise Exception(_strerror(rv))
+
+    def __dealloc__(self):
+        cnghttp2.nghttp2_hd_deflate_del(self._deflater)
+
+    def deflate(self, headers):
+        '''Compresses the |headers|. The |headers| must be sequence of tuple
+        of name/value pair, which are sequence of bytes (not unicode
+        string).
+
+        This function returns the encoded header block in byte string.
+        An exception will be raised on error.
+
+        '''
+        cdef cnghttp2.nghttp2_nv *nva = <cnghttp2.nghttp2_nv*>\
+                                        malloc(sizeof(cnghttp2.nghttp2_nv)*\
+                                        len(headers))
+        cdef cnghttp2.nghttp2_nv *nvap = nva
+
+        for k, v in headers:
+            nvap[0].name = k
+            nvap[0].namelen = len(k)
+            nvap[0].value = v
+            nvap[0].valuelen = len(v)
+            nvap[0].flags = cnghttp2.NGHTTP2_NV_FLAG_NONE
+            nvap += 1
+
+        cdef size_t outcap = 0
+        cdef ssize_t rv
+        cdef uint8_t *out
+        cdef size_t outlen
+
+        outlen = cnghttp2.nghttp2_hd_deflate_bound(self._deflater,
+                                                   nva, len(headers))
+
+        out = <uint8_t*>malloc(outlen)
+
+        rv = cnghttp2.nghttp2_hd_deflate_hd(self._deflater, out, outlen,
+                                            nva, len(headers))
+        free(nva)
+
+        if rv < 0:
+            free(out)
+
+            raise Exception(_strerror(rv))
+
+        cdef bytes res
+
+        try:
+            res = out[:rv]
+        finally:
+            free(out)
+
+        return res
+
+    def change_table_size(self, hd_table_bufsize_max):
+        '''Changes header table size to |hd_table_bufsize_max| byte.
+
+        An exception will be raised on error.
+
+        '''
+        cdef int rv
+        rv = cnghttp2.nghttp2_hd_deflate_change_table_size(self._deflater,
+                                                           hd_table_bufsize_max)
+        if rv != 0:
+            raise Exception(_strerror(rv))
+
+    def get_hd_table(self):
+        '''Returns copy of current dynamic header table.'''
+        return _get_hd_table(&self._deflater.ctx)
+
+cdef class HDInflater:
+    '''Performs header decompression.
+
+    The following example shows how to compress request header sets:
+
+        data = b'0082c5ad82bd0f000362617a0362757a'
+        inflater = nghttp2.HDInflater()
+        hdrs = inflater.inflate(data)
+        print(hdrs)
+
+    '''
+
+    cdef cnghttp2.nghttp2_hd_inflater *_inflater
+
+    def __cinit__(self):
+        rv = cnghttp2.nghttp2_hd_inflate_new(&self._inflater)
+        if rv != 0:
+            raise Exception(_strerror(rv))
+
+    def __dealloc__(self):
+        cnghttp2.nghttp2_hd_inflate_del(self._inflater)
+
+    def inflate(self, data):
+        '''Decompresses the compressed header block |data|. The |data| must be
+        byte string (not unicode string).
+
+        '''
+        cdef cnghttp2.nghttp2_nv nv
+        cdef int inflate_flags
+        cdef ssize_t rv
+        cdef uint8_t *buf = data
+        cdef size_t buflen = len(data)
+        res = []
+        while True:
+            inflate_flags = 0
+            rv = cnghttp2.nghttp2_hd_inflate_hd(self._inflater, &nv,
+                                                &inflate_flags,
+                                                buf, buflen, 1)
+            if rv < 0:
+                raise Exception(_strerror(rv))
+            buf += rv
+            buflen -= rv
+            if inflate_flags & cnghttp2.NGHTTP2_HD_INFLATE_EMIT:
+                # may throw
+                res.append((nv.name[:nv.namelen], nv.value[:nv.valuelen]))
+            if inflate_flags & cnghttp2.NGHTTP2_HD_INFLATE_FINAL:
+                break
+
+        cnghttp2.nghttp2_hd_inflate_end_headers(self._inflater)
+        return res
+
+    def change_table_size(self, hd_table_bufsize_max):
+        '''Changes header table size to |hd_table_bufsize_max| byte.
+
+        An exception will be raised on error.
+
+        '''
+        cdef int rv
+        rv = cnghttp2.nghttp2_hd_inflate_change_table_size(self._inflater,
+                                                           hd_table_bufsize_max)
+        if rv != 0:
+            raise Exception(_strerror(rv))
+
+    def get_hd_table(self):
+        '''Returns copy of current dynamic header table.'''
+        return _get_hd_table(&self._inflater.ctx)
+
+cdef _strerror(int liberror_code):
+    return cnghttp2.nghttp2_strerror(liberror_code).decode('utf-8')
+
+def print_hd_table(hdtable):
+    '''Convenient function to print |hdtable| to the standard output. This
+    function does not work if header name/value cannot be decoded using
+    UTF-8 encoding.
+
+    s=N means the entry occupies N bytes in header table.
+
+    '''
+    idx = 0
+    for entry in hdtable:
+        idx += 1
+        print('[{}] (s={}) {}: {}'\
+              .format(idx, entry.space(),
+                      entry.name.decode('utf-8'),
+                      entry.value.decode('utf-8')))
+
+try:
+    import socket
+    import io
+    import asyncio
+    import traceback
+    import sys
+    import email.utils
+    import datetime
+    import time
+    from urllib.parse import urlparse
+except ImportError:
+    asyncio = None
+
+def wrap_body(body):
+    if body is None:
+        return body
+    elif isinstance(body, str):
+        return io.BytesIO(body.encode('utf-8'))
+    elif isinstance(body, bytes):
+        return io.BytesIO(body)
+    elif isinstance(body, io.IOBase):
+        return body
+    else:
+        raise Exception(('body must be None or instance of str or '
+                            'bytes or io.IOBase'))
+
+
+cdef _get_stream_user_data(cnghttp2.nghttp2_session *session,
+                           int32_t stream_id):
+    cdef void *stream_user_data
+
+    stream_user_data = cnghttp2.nghttp2_session_get_stream_user_data\
+                       (session, stream_id)
+    if stream_user_data == NULL:
+        return None
+
+    return <object>stream_user_data
+
+cdef size_t _make_nva(cnghttp2.nghttp2_nv **nva_ptr, headers):
+    cdef cnghttp2.nghttp2_nv *nva
+    cdef size_t nvlen
+
+    nvlen = len(headers)
+    nva = <cnghttp2.nghttp2_nv*>malloc(sizeof(cnghttp2.nghttp2_nv) * nvlen)
+    for i, (k, v) in enumerate(headers):
+        nva[i].name = k
+        nva[i].namelen = len(k)
+        nva[i].value = v
+        nva[i].valuelen = len(v)
+        nva[i].flags = cnghttp2.NGHTTP2_NV_FLAG_NONE
+
+    nva_ptr[0] = nva
+
+    return nvlen
+
+cdef int server_on_header(cnghttp2.nghttp2_session *session,
+                          const cnghttp2.nghttp2_frame *frame,
+                          const uint8_t *name, size_t namelen,
+                          const uint8_t *value, size_t valuelen,
+                          uint8_t flags,
+                          void *user_data):
+    cdef http2 = <_HTTP2SessionCoreBase>user_data
+    logging.debug('server_on_header, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    handler = _get_stream_user_data(session, frame.hd.stream_id)
+    return on_header(name, namelen, value, valuelen, flags, handler)
+
+cdef int client_on_header(cnghttp2.nghttp2_session *session,
+                          const cnghttp2.nghttp2_frame *frame,
+                          const uint8_t *name, size_t namelen,
+                          const uint8_t *value, size_t valuelen,
+                          uint8_t flags,
+                          void *user_data):
+    cdef http2 = <_HTTP2SessionCoreBase>user_data
+    logging.debug('client_on_header, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    if frame.hd.type == cnghttp2.NGHTTP2_HEADERS:
+        if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_REQUEST:
+            return 0
+        handler = _get_stream_user_data(session, frame.hd.stream_id)
+    elif frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE:
+        handler = _get_stream_user_data(session, frame.push_promise.promised_stream_id)
+
+    return on_header(name, namelen, value, valuelen, flags, handler)
+
+
+cdef int on_header(const uint8_t *name, size_t namelen,
+                          const uint8_t *value, size_t valuelen,
+                          uint8_t flags,
+                          object handler):
+    if not handler:
+        return 0
+
+    key = name[:namelen]
+    values = value[:valuelen].split(b'\x00')
+    if key == b':scheme':
+        handler.scheme = values[0]
+    elif key == b':method':
+        handler.method = values[0]
+    elif key == b':authority' or key == b'host':
+        handler.host = values[0]
+    elif key == b':path':
+        handler.path = values[0]
+    elif key == b':status':
+        handler.status = values[0]
+
+    if key == b'cookie':
+        handler.cookies.extend(values)
+    else:
+        for v in values:
+            handler.headers.append((key, v))
+
+    return 0
+
+cdef int server_on_begin_request_headers(cnghttp2.nghttp2_session *session,
+                                         const cnghttp2.nghttp2_frame *frame,
+                                         void *user_data):
+    cdef http2 = <_HTTP2SessionCore>user_data
+
+    handler = http2._make_handler(frame.hd.stream_id)
+    cnghttp2.nghttp2_session_set_stream_user_data(session, frame.hd.stream_id,
+                                                  <void*>handler)
+
+    return 0
+
+cdef int server_on_begin_headers(cnghttp2.nghttp2_session *session,
+                                 const cnghttp2.nghttp2_frame *frame,
+                                 void *user_data):
+    if frame.hd.type == cnghttp2.NGHTTP2_HEADERS:
+        if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_REQUEST:
+            return server_on_begin_request_headers(session, frame, user_data)
+
+    return 0
+
+cdef int server_on_frame_recv(cnghttp2.nghttp2_session *session,
+                              const cnghttp2.nghttp2_frame *frame,
+                              void *user_data):
+    cdef http2 = <_HTTP2SessionCore>user_data
+    logging.debug('server_on_frame_recv, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    if frame.hd.type == cnghttp2.NGHTTP2_DATA:
+        if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM:
+            handler = _get_stream_user_data(session, frame.hd.stream_id)
+            if not handler:
+                return 0
+            try:
+                handler.on_request_done()
+            except:
+                sys.stderr.write(traceback.format_exc())
+                return http2._rst_stream(frame.hd.stream_id)
+    elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS:
+        if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_REQUEST:
+            handler = _get_stream_user_data(session, frame.hd.stream_id)
+            if not handler:
+                return 0
+            # Check required header fields. We expect that :authority
+            # or host header field.
+            if handler.scheme is None or handler.method is None or\
+               handler.host is None or handler.path is None:
+                return http2._rst_stream(frame.hd.stream_id,
+                                         cnghttp2.NGHTTP2_PROTOCOL_ERROR)
+            if handler.cookies:
+                handler.headers.append((b'cookie',
+                                        b'; '.join(handler.cookies)))
+                handler.cookies = None
+            try:
+                handler.on_headers()
+                if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM:
+                    handler.on_request_done()
+            except:
+                sys.stderr.write(traceback.format_exc())
+                return http2._rst_stream(frame.hd.stream_id)
+    elif frame.hd.type == cnghttp2.NGHTTP2_SETTINGS:
+        if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK):
+            http2._stop_settings_timer()
+
+    return 0
+
+cdef int on_data_chunk_recv(cnghttp2.nghttp2_session *session,
+                                   uint8_t flags,
+                                   int32_t stream_id, const uint8_t *data,
+                                   size_t length, void *user_data):
+    cdef http2 = <_HTTP2SessionCoreBase>user_data
+
+    handler = _get_stream_user_data(session, stream_id)
+    if not handler:
+        return 0
+
+    try:
+        handler.on_data(data[:length])
+    except:
+        sys.stderr.write(traceback.format_exc())
+        return http2._rst_stream(stream_id)
+
+    return 0
+
+cdef int server_on_frame_send(cnghttp2.nghttp2_session *session,
+                              const cnghttp2.nghttp2_frame *frame,
+                              void *user_data):
+    cdef http2 = <_HTTP2SessionCore>user_data
+    logging.debug('server_on_frame_send, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    if frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE:
+        # For PUSH_PROMISE, send push response immediately
+        handler = _get_stream_user_data\
+                  (session, frame.push_promise.promised_stream_id)
+        if not handler:
+            return 0
+
+        http2.send_response(handler)
+    elif frame.hd.type == cnghttp2.NGHTTP2_SETTINGS:
+        if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK) != 0:
+            return 0
+        http2._start_settings_timer()
+
+cdef int server_on_frame_not_send(cnghttp2.nghttp2_session *session,
+                                  const cnghttp2.nghttp2_frame *frame,
+                                  int lib_error_code,
+                                  void *user_data):
+    cdef http2 = <_HTTP2SessionCore>user_data
+    logging.debug('server_on_frame_not_send, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    if frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE:
+        # We have to remove handler here. Without this, it is not
+        # removed until session is terminated.
+        handler = _get_stream_user_data\
+                  (session, frame.push_promise.promised_stream_id)
+        if not handler:
+            return 0
+        http2._remove_handler(handler)
+
+cdef int on_stream_close(cnghttp2.nghttp2_session *session,
+                                int32_t stream_id,
+                                uint32_t error_code,
+                                void *user_data):
+    cdef http2 = <_HTTP2SessionCoreBase>user_data
+    logging.debug('on_stream_close, stream_id:%s', stream_id)
+
+    handler = _get_stream_user_data(session, stream_id)
+    if not handler:
+        return 0
+
+    try:
+        handler.on_close(error_code)
+    except:
+        sys.stderr.write(traceback.format_exc())
+
+    http2._remove_handler(handler)
+
+    return 0
+
+cdef ssize_t server_data_source_read(cnghttp2.nghttp2_session *session,
+                                     int32_t stream_id,
+                                     uint8_t *buf, size_t length,
+                                     uint32_t *data_flags,
+                                     cnghttp2.nghttp2_data_source *source,
+                                     void *user_data):
+    cdef http2 = <_HTTP2SessionCore>user_data
+    handler = <object>source.ptr
+
+    try:
+        data = handler.response_body.read(length)
+    except:
+        sys.stderr.write(traceback.format_exc())
+        return cnghttp2.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+
+    if data:
+        nread = len(data)
+        memcpy(buf, <uint8_t*>data, nread)
+        return nread
+
+    data_flags[0] = cnghttp2.NGHTTP2_DATA_FLAG_EOF
+
+    return 0
+
+cdef int client_on_begin_headers(cnghttp2.nghttp2_session *session,
+                                 const cnghttp2.nghttp2_frame *frame,
+                                 void *user_data):
+    cdef http2 = <_HTTP2ClientSessionCore>user_data
+
+    if frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE:
+        # Generate a temporary handler until the headers are all received
+        push_handler = BaseResponseHandler()
+        http2._add_handler(push_handler, frame.push_promise.promised_stream_id)
+        cnghttp2.nghttp2_session_set_stream_user_data(session, frame.push_promise.promised_stream_id,
+                                                      <void*>push_handler)
+
+    return 0
+
+cdef int client_on_frame_recv(cnghttp2.nghttp2_session *session,
+                              const cnghttp2.nghttp2_frame *frame,
+                              void *user_data):
+    cdef http2 = <_HTTP2ClientSessionCore>user_data
+    logging.debug('client_on_frame_recv, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    if frame.hd.type == cnghttp2.NGHTTP2_DATA:
+        if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM:
+            handler = _get_stream_user_data(session, frame.hd.stream_id)
+            if not handler:
+                return 0
+            try:
+                handler.on_response_done()
+            except:
+                sys.stderr.write(traceback.format_exc())
+                return http2._rst_stream(frame.hd.stream_id)
+    elif frame.hd.type == cnghttp2.NGHTTP2_HEADERS:
+        if frame.headers.cat == cnghttp2.NGHTTP2_HCAT_RESPONSE:
+            handler = _get_stream_user_data(session, frame.hd.stream_id)
+
+            if not handler:
+                return 0
+            # Check required header fields. We expect a status.
+            if handler.status is None:
+                return http2._rst_stream(frame.hd.stream_id,
+                                         cnghttp2.NGHTTP2_PROTOCOL_ERROR)
+            if handler.cookies:
+                handler.headers.append((b'cookie',
+                                        b'; '.join(handler.cookies)))
+                handler.cookies = None
+            try:
+                handler.on_headers()
+                if frame.hd.flags & cnghttp2.NGHTTP2_FLAG_END_STREAM:
+                    handler.on_response_done()
+            except:
+                sys.stderr.write(traceback.format_exc())
+                return http2._rst_stream(frame.hd.stream_id)
+    elif frame.hd.type == cnghttp2.NGHTTP2_SETTINGS:
+        if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK):
+            http2._stop_settings_timer()
+    elif frame.hd.type == cnghttp2.NGHTTP2_PUSH_PROMISE:
+        handler = _get_stream_user_data(session, frame.hd.stream_id)
+        if not handler:
+            return 0
+        # Get the temporary push_handler which now should have all of the header data
+        push_handler = _get_stream_user_data(session, frame.push_promise.promised_stream_id)
+        if not push_handler:
+            return 0
+        # Remove the temporary handler
+        http2._remove_handler(push_handler)
+        cnghttp2.nghttp2_session_set_stream_user_data(session, frame.push_promise.promised_stream_id,
+                                                      <void*>NULL)
+
+        if push_handler.scheme is None or push_handler.method is None or\
+           push_handler.host is None or push_handler.path is None:
+            return http2._rst_stream(frame.push_promise.promised_stream_id,
+                                     cnghttp2.NGHTTP2_PROTOCOL_ERROR)
+        try:
+            handler.on_push_promise(push_handler)
+        except:
+            sys.stderr.write(traceback.format_exc())
+            return http2._rst_stream(frame.hd.stream_id)
+
+    return 0
+
+cdef int client_on_frame_send(cnghttp2.nghttp2_session *session,
+                              const cnghttp2.nghttp2_frame *frame,
+                              void *user_data):
+    cdef http2 = <_HTTP2ClientSessionCore>user_data
+    logging.debug('client_on_frame_send, type:%s, stream_id:%s', frame.hd.type, frame.hd.stream_id)
+
+    if frame.hd.type == cnghttp2.NGHTTP2_SETTINGS:
+        if (frame.hd.flags & cnghttp2.NGHTTP2_FLAG_ACK) != 0:
+            return 0
+        http2._start_settings_timer()
+
+cdef ssize_t client_data_source_read(cnghttp2.nghttp2_session *session,
+                                     int32_t stream_id,
+                                     uint8_t *buf, size_t length,
+                                     uint32_t *data_flags,
+                                     cnghttp2.nghttp2_data_source *source,
+                                     void *user_data):
+    cdef http2 = <_HTTP2ClientSessionCore>user_data
+    body = <object>source.ptr
+
+    try:
+        data = body.read(length)
+    except:
+        sys.stderr.write(traceback.format_exc())
+        return cnghttp2.NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+
+    if data:
+        nread = len(data)
+        memcpy(buf, <uint8_t*>data, nread)
+        return nread
+
+    data_flags[0] = cnghttp2.NGHTTP2_DATA_FLAG_EOF
+
+    return 0
+
+cdef class _HTTP2SessionCoreBase:
+    cdef cnghttp2.nghttp2_session *session
+    cdef transport
+    cdef handler_class
+    cdef handlers
+    cdef settings_timer
+
+    def __cinit__(self, transport, handler_class=None):
+        self.session = NULL
+        self.transport = transport
+        self.handler_class = handler_class
+        self.handlers = set()
+        self.settings_timer = None
+
+    def __dealloc__(self):
+        cnghttp2.nghttp2_session_del(self.session)
+
+    def data_received(self, data):
+        cdef ssize_t rv
+
+        rv = cnghttp2.nghttp2_session_mem_recv(self.session, data, len(data))
+        if rv < 0:
+            raise Exception('nghttp2_session_mem_recv failed: {}'.format\
+                            (_strerror(rv)))
+        self.send_data()
+
+    OUTBUF_MAX = 65535
+    SETTINGS_TIMEOUT = 5.0
+
+    def send_data(self):
+        cdef ssize_t outbuflen
+        cdef const uint8_t *outbuf
+
+        while True:
+            if self.transport.get_write_buffer_size() > self.OUTBUF_MAX:
+                break
+            outbuflen = cnghttp2.nghttp2_session_mem_send(self.session, &outbuf)
+            if outbuflen == 0:
+                break
+            if outbuflen < 0:
+                raise Exception('nghttp2_session_mem_send faild: {}'.format\
+                                (_strerror(outbuflen)))
+            self.transport.write(outbuf[:outbuflen])
+
+        if self.transport.get_write_buffer_size() == 0 and \
+           cnghttp2.nghttp2_session_want_read(self.session) == 0 and \
+           cnghttp2.nghttp2_session_want_write(self.session) == 0:
+            self.transport.close()
+
+    def _make_handler(self, stream_id):
+        logging.debug('_make_handler, stream_id:%s', stream_id)
+        handler = self.handler_class(self, stream_id)
+        self.handlers.add(handler)
+        return handler
+
+    def _remove_handler(self, handler):
+        logging.debug('_remove_handler, stream_id:%s', handler.stream_id)
+        self.handlers.remove(handler)
+
+    def _add_handler(self, handler, stream_id):
+        logging.debug('_add_handler, stream_id:%s', stream_id)
+        handler.stream_id = stream_id
+        handler.http2 = self
+        handler.remote_address = self._get_remote_address()
+        self.handlers.add(handler)
+
+    def _rst_stream(self, stream_id,
+                   error_code=cnghttp2.NGHTTP2_INTERNAL_ERROR):
+        cdef int rv
+
+        rv = cnghttp2.nghttp2_submit_rst_stream\
+             (self.session, cnghttp2.NGHTTP2_FLAG_NONE,
+              stream_id, error_code)
+
+        return rv
+
+    def _get_remote_address(self):
+        return self.transport.get_extra_info('peername')
+
+    def _start_settings_timer(self):
+        loop = asyncio.get_event_loop()
+        self.settings_timer = loop.call_later(self.SETTINGS_TIMEOUT,
+                                              self._settings_timeout)
+
+    def _stop_settings_timer(self):
+        if self.settings_timer:
+            self.settings_timer.cancel()
+            self.settings_timer = None
+
+    def _settings_timeout(self):
+        cdef int rv
+
+        logging.debug('_settings_timeout')
+
+        self.settings_timer = None
+
+        rv = cnghttp2.nghttp2_session_terminate_session\
+             (self.session, cnghttp2.NGHTTP2_SETTINGS_TIMEOUT)
+        try:
+            self.send_data()
+        except Exception as err:
+            sys.stderr.write(traceback.format_exc())
+            self.transport.close()
+            return
+
+    def _log_request(self, handler):
+        now = datetime.datetime.now()
+        tv = time.mktime(now.timetuple())
+        datestr = email.utils.formatdate(timeval=tv, localtime=False,
+                                        usegmt=True)
+        try:
+            method = handler.method.decode('utf-8')
+        except:
+            method = handler.method
+        try:
+            path = handler.path.decode('utf-8')
+        except:
+            path = handler.path
+        logging.info('%s - - [%s] "%s %s HTTP/2" %s - %s', handler.remote_address[0],
+                          datestr, method, path, handler.status,
+                          'P' if handler.pushed else '-')
+
+    def close(self):
+        rv = cnghttp2.nghttp2_session_terminate_session\
+             (self.session, cnghttp2.NGHTTP2_NO_ERROR)
+        try:
+            self.send_data()
+        except Exception as err:
+            sys.stderr.write(traceback.format_exc())
+            self.transport.close()
+            return
+
+cdef class _HTTP2SessionCore(_HTTP2SessionCoreBase):
+    def __cinit__(self, *args, **kwargs):
+        cdef cnghttp2.nghttp2_session_callbacks *callbacks
+        cdef cnghttp2.nghttp2_settings_entry iv[2]
+        cdef int rv
+
+        super(_HTTP2SessionCore, self).__init__(*args, **kwargs)
+
+        rv = cnghttp2.nghttp2_session_callbacks_new(&callbacks)
+
+        if rv != 0:
+            raise Exception('nghttp2_session_callbacks_new failed: {}'.format\
+                            (_strerror(rv)))
+
+        cnghttp2.nghttp2_session_callbacks_set_on_header_callback(
+            callbacks, server_on_header)
+        cnghttp2.nghttp2_session_callbacks_set_on_begin_headers_callback(
+            callbacks, server_on_begin_headers)
+        cnghttp2.nghttp2_session_callbacks_set_on_frame_recv_callback(
+            callbacks, server_on_frame_recv)
+        cnghttp2.nghttp2_session_callbacks_set_on_stream_close_callback(
+            callbacks, on_stream_close)
+        cnghttp2.nghttp2_session_callbacks_set_on_frame_send_callback(
+            callbacks, server_on_frame_send)
+        cnghttp2.nghttp2_session_callbacks_set_on_frame_not_send_callback(
+            callbacks, server_on_frame_not_send)
+        cnghttp2.nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+            callbacks, on_data_chunk_recv)
+
+        rv = cnghttp2.nghttp2_session_server_new(&self.session, callbacks,
+                                                 <void*>self)
+
+        cnghttp2.nghttp2_session_callbacks_del(callbacks)
+
+        if rv != 0:
+            raise Exception('nghttp2_session_server_new failed: {}'.format\
+                            (_strerror(rv)))
+
+        iv[0].settings_id = cnghttp2.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS
+        iv[0].value = 100
+        iv[1].settings_id = cnghttp2.NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE
+        iv[1].value = cnghttp2.NGHTTP2_INITIAL_WINDOW_SIZE
+
+        rv = cnghttp2.nghttp2_submit_settings(self.session,
+                                              cnghttp2.NGHTTP2_FLAG_NONE,
+                                              iv, sizeof(iv) / sizeof(iv[0]))
+
+        if rv != 0:
+            raise Exception('nghttp2_submit_settings failed: {}'.format\
+                            (_strerror(rv)))
+
+    def send_response(self, handler):
+        cdef cnghttp2.nghttp2_data_provider prd
+        cdef cnghttp2.nghttp2_data_provider *prd_ptr
+        cdef cnghttp2.nghttp2_nv *nva
+        cdef size_t nvlen
+        cdef int rv
+
+        logging.debug('send_response, stream_id:%s', handler.stream_id)
+
+        nva = NULL
+        nvlen = _make_nva(&nva, handler.response_headers)
+
+        if handler.response_body:
+            prd.source.ptr = <void*>handler
+            prd.read_callback = server_data_source_read
+            prd_ptr = &prd
+        else:
+            prd_ptr = NULL
+
+        rv = cnghttp2.nghttp2_submit_response(self.session, handler.stream_id,
+                                              nva, nvlen, prd_ptr)
+
+        free(nva)
+
+        if rv != 0:
+            # TODO Ignore return value
+            self._rst_stream(handler.stream_id)
+            raise Exception('nghttp2_submit_response failed: {}'.format\
+                            (_strerror(rv)))
+
+        self._log_request(handler)
+
+    def push(self, handler, promised_handler):
+        cdef cnghttp2.nghttp2_nv *nva
+        cdef size_t nvlen
+        cdef int32_t promised_stream_id
+
+        self.handlers.add(promised_handler)
+
+        nva = NULL
+        nvlen = _make_nva(&nva, promised_handler.headers)
+
+        promised_stream_id = cnghttp2.nghttp2_submit_push_promise\
+                             (self.session,
+                              cnghttp2.NGHTTP2_FLAG_NONE,
+                              handler.stream_id,
+                              nva, nvlen,
+                              <void*>promised_handler)
+        if promised_stream_id < 0:
+            raise Exception('nghttp2_submit_push_promise failed: {}'.format\
+                            (_strerror(promised_stream_id)))
+
+        promised_handler.stream_id = promised_stream_id
+
+        logging.debug('push, stream_id:%s', promised_stream_id)
+
+        return promised_handler
+
+cdef class _HTTP2ClientSessionCore(_HTTP2SessionCoreBase):
+    def __cinit__(self, *args, **kwargs):
+        cdef cnghttp2.nghttp2_session_callbacks *callbacks
+        cdef cnghttp2.nghttp2_settings_entry iv[2]
+        cdef int rv
+
+        super(_HTTP2ClientSessionCore, self).__init__(*args, **kwargs)
+
+        rv = cnghttp2.nghttp2_session_callbacks_new(&callbacks)
+
+        if rv != 0:
+            raise Exception('nghttp2_session_callbacks_new failed: {}'.format\
+                            (_strerror(rv)))
+
+        cnghttp2.nghttp2_session_callbacks_set_on_header_callback(
+            callbacks, client_on_header)
+        cnghttp2.nghttp2_session_callbacks_set_on_begin_headers_callback(
+            callbacks, client_on_begin_headers)
+        cnghttp2.nghttp2_session_callbacks_set_on_frame_recv_callback(
+            callbacks, client_on_frame_recv)
+        cnghttp2.nghttp2_session_callbacks_set_on_stream_close_callback(
+            callbacks, on_stream_close)
+        cnghttp2.nghttp2_session_callbacks_set_on_frame_send_callback(
+            callbacks, client_on_frame_send)
+        cnghttp2.nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+            callbacks, on_data_chunk_recv)
+
+        rv = cnghttp2.nghttp2_session_client_new(&self.session, callbacks,
+                                                 <void*>self)
+
+        cnghttp2.nghttp2_session_callbacks_del(callbacks)
+
+        if rv != 0:
+            raise Exception('nghttp2_session_client_new failed: {}'.format\
+                            (_strerror(rv)))
+
+        iv[0].settings_id = cnghttp2.NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS
+        iv[0].value = 100
+        iv[1].settings_id = cnghttp2.NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE
+        iv[1].value = cnghttp2.NGHTTP2_INITIAL_WINDOW_SIZE
+
+        rv = cnghttp2.nghttp2_submit_settings(self.session,
+                                              cnghttp2.NGHTTP2_FLAG_NONE,
+                                              iv, sizeof(iv) / sizeof(iv[0]))
+
+        if rv != 0:
+            raise Exception('nghttp2_submit_settings failed: {}'.format\
+                            (_strerror(rv)))
+
+    def send_request(self, method, scheme, host, path, headers, body, handler):
+        cdef cnghttp2.nghttp2_data_provider prd
+        cdef cnghttp2.nghttp2_data_provider *prd_ptr
+        cdef cnghttp2.nghttp2_priority_spec *pri_ptr
+        cdef cnghttp2.nghttp2_nv *nva
+        cdef size_t nvlen
+        cdef int32_t stream_id
+
+        body = wrap_body(body)
+
+        custom_headers = _encode_headers(headers)
+        headers = [
+            (b':scheme', scheme.encode('utf-8')),
+            (b':method', method.encode('utf-8')),
+            (b':authority', host.encode('utf-8')),
+            (b':path', path.encode('utf-8'))
+        ]
+        headers.extend(custom_headers)
+
+        nva = NULL
+        nvlen = _make_nva(&nva, headers)
+
+        if body:
+            prd.source.ptr = <void*>body
+            prd.read_callback = client_data_source_read
+            prd_ptr = &prd
+        else:
+            prd_ptr = NULL
+
+        # TODO: Enable priorities
+        pri_ptr = NULL
+
+        stream_id = cnghttp2.nghttp2_submit_request\
+                             (self.session, pri_ptr,
+                              nva, nvlen, prd_ptr,
+                              <void*>handler)
+        free(nva)
+
+        if stream_id < 0:
+            raise Exception('nghttp2_submit_request failed: {}'.format\
+                            (_strerror(stream_id)))
+
+        logging.debug('request, stream_id:%s', stream_id)
+
+        self._add_handler(handler, stream_id)
+        cnghttp2.nghttp2_session_set_stream_user_data(self.session, stream_id,
+                                                  <void*>handler)
+
+        return handler
+
+    def push(self, push_promise, handler):
+        if handler:
+            # push_promise accepted, fill in the handler with the stored
+            # headers from the push_promise
+            handler.status = push_promise.status
+            handler.scheme = push_promise.scheme
+            handler.method = push_promise.method
+            handler.host = push_promise.host
+            handler.path = push_promise.path
+            handler.headers = push_promise.headers
+            handler.cookies = push_promise.cookies
+            handler.stream_id = push_promise.stream_id
+            handler.http2 = self
+            handler.pushed = True
+
+            self._add_handler(handler, handler.stream_id)
+
+            cnghttp2.nghttp2_session_set_stream_user_data(self.session, handler.stream_id,
+                                                      <void*>handler)
+        else:
+            # push_promise rejected, reset the stream
+            self._rst_stream(push_promise.stream_id,
+                              error_code=cnghttp2.NGHTTP2_NO_ERROR)
+
+if asyncio:
+
+    class BaseRequestHandler:
+
+        """HTTP/2 request (stream) handler base class.
+
+        The class is used to handle the HTTP/2 stream. By default, it does
+        not nothing. It must be subclassed to handle each event callback
+        method.
+
+        The first callback method invoked is on_headers(). It is called
+        when HEADERS frame, which includes request header fields, is
+        arrived.
+
+        If request has request body, on_data(data) is invoked for each
+        chunk of received data.
+
+        When whole request is received, on_request_done() is invoked.
+
+        When stream is closed, on_close(error_code) is called.
+
+        The application can send response using send_response() method. It
+        can be used in on_headers(), on_data() or on_request_done().
+
+        The application can push resource using push() method. It must be
+        used before send_response() call.
+
+        The following instance variables are available:
+
+        client_address
+          Contains a tuple of the form (host, port) referring to the client's
+          address.
+
+        stream_id
+          Stream ID of this stream
+
+        scheme
+          Scheme of the request URI. This is a value of :scheme header field.
+
+        method
+          Method of this stream. This is a value of :method header field.
+
+        host
+          This is a value of :authority or host header field.
+
+        path
+          This is a value of :path header field.
+
+        """
+
+        def __init__(self, http2, stream_id):
+            self.headers = []
+            self.cookies = []
+            # Stream ID. For promised stream, it is initially -1.
+            self.stream_id = stream_id
+            self.http2 = http2
+            # address of the client
+            self.remote_address = self.http2._get_remote_address()
+            # :scheme header field in request
+            self.scheme = None
+            # :method header field in request
+            self.method = None
+            # :authority or host header field in request
+            self.host = None
+            # :path header field in request
+            self.path = None
+            # HTTP status
+            self.status = None
+            # True if this is a handler for pushed resource
+            self.pushed = False
+
+        @property
+        def client_address(self):
+            return self.remote_address
+
+        def on_headers(self):
+
+            '''Called when request HEADERS is arrived.
+
+            '''
+            pass
+
+        def on_data(self, data):
+
+            '''Called when a chunk of request body is arrived. This method
+            will be called multiple times until all data are received.
+
+            '''
+            pass
+
+        def on_request_done(self):
+
+            '''Called when whole request was received
+
+            '''
+            pass
+
+        def on_close(self, error_code):
+
+            '''Called when stream is about to close.
+
+            '''
+            pass
+
+        def send_response(self, status=200, headers=None, body=None):
+
+            '''Send response. The status is HTTP status code. The headers is
+            additional response headers. The :status header field is
+            appended by the library. The body is the response body. It
+            could be None if response body is empty. Or it must be
+            instance of either str, bytes or io.IOBase. If instance of str
+            is specified, it is encoded using UTF-8.
+
+            The headers is a list of tuple of the form (name,
+            value). The name and value can be either unicode string or
+            byte string.
+
+            On error, exception will be thrown.
+
+            '''
+            if self.status is not None:
+                raise Exception('response has already been sent')
+
+            if not status:
+                raise Exception('status must not be empty')
+
+            body = wrap_body(body)
+
+            self._set_response_prop(status, headers, body)
+            self.http2.send_response(self)
+
+        def push(self, path, method='GET', request_headers=None,
+                 status=200, headers=None, body=None):
+
+            '''Push a resource. The path is a path portion of request URI
+            for this
+            resource. The method is a method to access this
+            resource. The request_headers is additional request
+            headers to access this resource. The :scheme, :method,
+            :authority and :path are appended by the library. The
+            :scheme and :authority are inherited from the request (not
+            request_headers parameter).
+
+            The status is HTTP status code. The headers is additional
+            response headers. The :status header field is appended by the
+            library. The body is the response body. It could be None if
+            response body is empty. Or it must be instance of either str,
+            bytes or io.IOBase. If instance of str is specified, it is
+            encoded using UTF-8.
+
+            The headers and request_headers are a list of tuple of the
+            form (name, value). The name and value can be either
+            unicode string or byte string.
+
+            On error, exception will be thrown.
+
+            '''
+            if not status:
+                raise Exception('status must not be empty')
+
+            if not method:
+                raise Exception('method must not be empty')
+
+            if not path:
+                raise Exception('path must not be empty')
+
+            body = wrap_body(body)
+
+            promised_handler = self.http2._make_handler(-1)
+            promised_handler.pushed = True
+            promised_handler.scheme = self.scheme
+            promised_handler.method = method.encode('utf-8')
+            promised_handler.host = self.host
+            promised_handler.path = path.encode('utf-8')
+            promised_handler._set_response_prop(status, headers, body)
+
+            if request_headers is None:
+                request_headers = []
+
+            request_headers = _encode_headers(request_headers)
+            request_headers.append((b':scheme', promised_handler.scheme))
+            request_headers.append((b':method', promised_handler.method))
+            request_headers.append((b':authority', promised_handler.host))
+            request_headers.append((b':path', promised_handler.path))
+
+            promised_handler.headers = request_headers
+
+            return self.http2.push(self, promised_handler)
+
+        def _set_response_prop(self, status, headers, body):
+            self.status = status
+
+            if headers is None:
+                headers = []
+
+            self.response_headers = _encode_headers(headers)
+            self.response_headers.append((b':status', str(status)\
+                                          .encode('utf-8')))
+
+            self.response_body = body
+
+    def _encode_headers(headers):
+        if not headers:
+            return []
+        return [(k if isinstance(k, bytes) else k.encode('utf-8'),
+                 v if isinstance(v, bytes) else v.encode('utf-8')) \
+                for k, v in headers]
+
+    class _HTTP2Session(asyncio.Protocol):
+
+        def __init__(self, RequestHandlerClass):
+            asyncio.Protocol.__init__(self)
+            self.RequestHandlerClass = RequestHandlerClass
+            self.http2 = None
+
+        def connection_made(self, transport):
+            address = transport.get_extra_info('peername')
+            logging.info('connection_made, address:%s, port:%s', address[0], address[1])
+
+            self.transport = transport
+            self.connection_header = cnghttp2.NGHTTP2_CLIENT_CONNECTION_PREFACE
+            sock = self.transport.get_extra_info('socket')
+            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+            ssl_ctx = self.transport.get_extra_info('sslcontext')
+            if ssl_ctx:
+                protocol = sock.selected_npn_protocol()
+                logging.info('npn, protocol:%s', protocol)
+                if protocol.encode('utf-8') != \
+                   cnghttp2.NGHTTP2_PROTO_VERSION_ID:
+                    self.transport.abort()
+
+        def connection_lost(self, exc):
+            logging.info('connection_lost')
+            if self.http2:
+                self.http2 = None
+
+        def data_received(self, data):
+            nread = min(len(data), len(self.connection_header))
+
+            if self.connection_header.startswith(data[:nread]):
+                data = data[nread:]
+                self.connection_header = self.connection_header[nread:]
+                if len(self.connection_header) == 0:
+                    try:
+                        self.http2 = _HTTP2SessionCore\
+                                     (self.transport,
+                                      self.RequestHandlerClass)
+                    except Exception as err:
+                        sys.stderr.write(traceback.format_exc())
+                        self.transport.abort()
+                        return
+
+                    self.data_received = self.data_received2
+                    self.resume_writing = self.resume_writing2
+                    self.data_received(data)
+            else:
+                self.transport.abort()
+
+        def data_received2(self, data):
+            try:
+                self.http2.data_received(data)
+            except Exception as err:
+                sys.stderr.write(traceback.format_exc())
+                self.transport.close()
+                return
+
+        def resume_writing2(self):
+            try:
+                self.http2.send_data()
+            except Exception as err:
+                sys.stderr.write(traceback.format_exc())
+                self.transport.close()
+                return
+
+    class HTTP2Server:
+
+        '''HTTP/2 server.
+
+        This class builds on top of the asyncio event loop. On
+        construction, RequestHandlerClass must be given, which must be a
+        subclass of BaseRequestHandler class.
+
+        '''
+        def __init__(self, address, RequestHandlerClass, ssl=None):
+
+            '''address is a tuple of the listening address and port (e.g.,
+            ('127.0.0.1', 8080)). RequestHandlerClass must be a subclass
+            of BaseRequestHandler class to handle a HTTP/2 stream.  The
+            ssl can be ssl.SSLContext instance. If it is not None, the
+            resulting server is SSL/TLS capable.
+
+            '''
+            def session_factory():
+                return _HTTP2Session(RequestHandlerClass)
+
+            self.loop = asyncio.get_event_loop()
+
+            if ssl:
+                ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
+                                       .decode('utf-8')])
+
+            coro = self.loop.create_server(session_factory,
+                                           host=address[0], port=address[1],
+                                           ssl=ssl)
+            self.server = self.loop.run_until_complete(coro)
+            logging.info('listen, address:%s, port:%s', address[0], address[1])
+
+        def serve_forever(self):
+            try:
+                self.loop.run_forever()
+            finally:
+                self.server.close()
+                self.loop.close()
+
+
+
+    class BaseResponseHandler:
+
+        """HTTP/2 response (stream) handler base class.
+
+        The class is used to handle the HTTP/2 stream. By default, it does
+        not nothing. It must be subclassed to handle each event callback
+        method.
+
+        The first callback method invoked is on_headers(). It is called
+        when HEADERS frame, which includes response header fields, is
+        arrived.
+
+        If response has a body, on_data(data) is invoked for each
+        chunk of received data.
+
+        When whole response is received, on_response_done() is invoked.
+
+        When stream is closed, on_close(error_code) is called.
+
+        The application can send follow up requests using HTTP2Client.send_request() method.
+
+        The application can handle push resource using on_push_promise() method.
+
+        The following instance variables are available:
+
+        server_address
+          Contains a tuple of the form (host, port) referring to the server's
+          address.
+
+        stream_id
+          Stream ID of this stream
+
+        scheme
+          Scheme of the request URI. This is a value of :scheme header field.
+
+        method
+          Method of this stream. This is a value of :method header field.
+
+        host
+          This is a value of :authority or host header field.
+
+        path
+          This is a value of :path header field.
+
+        """
+
+        def __init__(self, http2=None, stream_id=-1):
+            self.headers = []
+            self.cookies = []
+            # Stream ID. For promised stream, it is initially -1.
+            self.stream_id = stream_id
+            self.http2 = http2
+            # address of the server
+            self.remote_address = None
+            # :scheme header field in request
+            self.scheme = None
+            # :method header field in request
+            self.method = None
+            # :authority or host header field in request
+            self.host = None
+            # :path header field in request
+            self.path = None
+            # HTTP status
+            self.status = None
+            # True if this is a handler for pushed resource
+            self.pushed = False
+
+        @property
+        def server_address(self):
+            return self.remote_address
+
+        def on_headers(self):
+
+            '''Called when response HEADERS is arrived.
+
+            '''
+            pass
+
+        def on_data(self, data):
+
+            '''Called when a chunk of response body is arrived. This method
+            will be called multiple times until all data are received.
+
+            '''
+            pass
+
+        def on_response_done(self):
+
+            '''Called when whole response was received
+
+            '''
+            pass
+
+        def on_close(self, error_code):
+
+            '''Called when stream is about to close.
+
+            '''
+            pass
+
+        def on_push_promise(self, push_promise):
+
+            '''Called when a push is promised. Default behavior is to
+            cancel the push. If application overrides this method,
+            it should call either accept_push or reject_push.
+
+            '''
+            self.reject_push(push_promise)
+
+        def reject_push(self, push_promise):
+
+            '''Convenience method equivalent to calling accept_push
+            with a falsy value.
+
+            '''
+            self.http2.push(push_promise, None)
+
+        def accept_push(self, push_promise, handler=None):
+
+            '''Accept a push_promise and provider a handler for the
+            new stream. If a falsy value is supplied for the handler,
+            the push is rejected.
+
+            '''
+            self.http2.push(push_promise, handler)
+
+    class _HTTP2ClientSession(asyncio.Protocol):
+
+        def __init__(self, client):
+            asyncio.Protocol.__init__(self)
+            self.http2 = None
+            self.pending = []
+            self.client = client
+
+        def connection_made(self, transport):
+            address = transport.get_extra_info('peername')
+            logging.info('connection_made, address:%s, port:%s', address[0], address[1])
+
+            self.transport = transport
+            sock = self.transport.get_extra_info('socket')
+            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+            ssl_ctx = self.transport.get_extra_info('sslcontext')
+            if ssl_ctx:
+                protocol = sock.selected_npn_protocol()
+                logging.info('npn, protocol:%s', protocol)
+                if protocol is None or protocol.encode('utf-8') != \
+                   cnghttp2.NGHTTP2_PROTO_VERSION_ID:
+                    self.transport.abort()
+
+            # Send preamble
+            self.transport.write(cnghttp2.NGHTTP2_CLIENT_CONNECTION_PREFACE)
+            self.http2 = _HTTP2ClientSessionCore(self.transport)
+
+           # Clear pending requests
+            send_pending = self.pending
+            self.pending = []
+            for method,scheme,host,path,headers,body,handler in send_pending:
+                self.send_request(method=method, scheme=scheme, host=host, path=path,\
+                                  headers=headers, body=body, handler=handler)
+            self.http2.send_data()
+
+        def connection_lost(self, exc):
+            logging.info('connection_lost')
+            if self.http2:
+                self.http2 = None
+            self.client.close()
+
+        def data_received(self, data):
+            try:
+                self.http2.data_received(data)
+            except Exception as err:
+                sys.stderr.write(traceback.format_exc())
+                self.transport.close()
+                return
+
+        def resume_writing(self):
+            try:
+                self.http2.send_data()
+            except Exception as err:
+                sys.stderr.write(traceback.format_exc())
+                self.transport.close()
+                return
+
+        def send_request(self, method, scheme, host, path, headers, body, handler):
+            try:
+               # Waiting until connection established
+                if not self.http2:
+                    self.pending.append([method, scheme, host, path, headers, body, handler])
+                    return
+
+                self.http2.send_request(method=method, scheme=scheme, host=host, path=path,\
+                                        headers=headers, body=body, handler=handler)
+                self.http2.send_data()
+            except Exception as err:
+                sys.stderr.write(traceback.format_exc())
+                self.transport.close()
+                return
+
+        def close(self):
+            if self.http2:
+                self.http2.close()
+
+
+    class HTTP2Client:
+
+        '''HTTP/2 client.
+
+        This class builds on top of the asyncio event loop.
+
+        '''
+        def __init__(self, address, loop=None, ssl=None):
+
+            '''address is a tuple of the connect address and port (e.g.,
+            ('127.0.0.1', 8080)). The ssl can be ssl.SSLContext instance.
+            If it is not None, the resulting client is SSL/TLS capable.
+            '''
+
+            self.address = address
+            self.session = _HTTP2ClientSession(self)
+            def session_factory():
+                return self.session
+
+            if ssl:
+                ssl.set_npn_protocols([cnghttp2.NGHTTP2_PROTO_VERSION_ID\
+                                       .decode('utf-8')])
+
+            self.loop = loop
+            if not self.loop:
+                self.loop = asyncio.get_event_loop()
+
+            coro = self.loop.create_connection(session_factory,
+                                           host=address[0], port=address[1],
+                                           ssl=ssl)
+
+            if ssl:
+                self.scheme = 'https'
+            else:
+                self.scheme = 'http'
+
+            self.transport,_ = self.loop.run_until_complete(coro)
+            logging.info('connect, address:%s, port:%s', self.address[0], self.address[1])
+
+        @property
+        def io_loop(self):
+            return self.loop
+
+        def close(self):
+            self.session.close()
+
+        def send_request(self, method='GET', url='/', headers=None, body=None, handler=None):
+            url = urlparse(url)
+            scheme = url.scheme if url.scheme else self.scheme
+            host = url.netloc if url.netloc else self.address[0]+':'+str(self.address[1])
+            path = url.path
+            if url.params:
+                path += ';'+url.params
+            if url.query:
+                path += '?'+url.query
+            if url.fragment:
+                path += '#'+url.fragment
+
+            self.session.send_request(method=method, scheme=scheme, host=host, path=path,\
+                                      headers=headers, body=body, handler=handler)
diff --git a/python/setup.py.in b/python/setup.py.in
new file mode 100644 (file)
index 0000000..8452fa7
--- /dev/null
@@ -0,0 +1,49 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import sys
+from distutils.core import setup
+from distutils.extension import Extension
+
+if sys.platform == "win32":
+  LIBS = ['nghttp2_imp', 'ws2_32']
+else:
+  LIBS = ['nghttp2']
+
+setup(
+    name = 'python-nghttp2',
+    description = 'Python HTTP/2 library on top of nghttp2',
+    author = 'Tatsuhiro Tsujikawa',
+    version = '@PACKAGE_VERSION@',
+    author_email = 'tatsuhiro.t@gmail.com',
+    url = 'http://tatsuhiro-t.github.io/nghttp2/',
+    keywords = [],
+    ext_modules = [Extension("nghttp2",
+                             ["nghttp2.c"],
+                             include_dirs=['@top_srcdir@/lib',
+                                           '@top_srcdir@/lib/includes',
+                                           '@top_builddir@/lib/includes'],
+                             library_dirs=['@top_builddir@/lib/.libs'],
+                             libraries=LIBS)],
+    long_description='TBD'
+    )
diff --git a/python/wsgi.py b/python/wsgi.py
new file mode 100644 (file)
index 0000000..8e19bad
--- /dev/null
@@ -0,0 +1,119 @@
+# nghttp2 - HTTP/2.0 C Library
+
+# Copyright (c) 2013 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+import io
+import sys
+from urllib.parse import urlparse
+
+import nghttp2
+
+def _dance_decode(b):
+    # TODO faster than looping through and mod-128'ing all unicode points?
+    return b.decode('utf-8').encode('latin1').decode('latin1')
+
+class WSGIContainer(nghttp2.BaseRequestHandler):
+
+    _BASE_ENVIRON = {
+        'wsgi.version': (1,0),
+        'wsgi.url_scheme': 'http', # FIXME
+        'wsgi.multithread': True, # TODO I think?
+        'wsgi.multiprocess': False, # TODO no idea
+        'wsgi.run_once': True, # TODO now I'm just guessing
+        'wsgi.errors': sys.stderr, # TODO will work for testing - is this even used by any frameworks?
+    }
+
+    def __init__(self, app, *args, **kwargs):
+        super(WSGIContainer, self).__init__(*args, **kwargs)
+        self.app = app
+        self.chunks = []
+
+    def on_data(self, chunk):
+        self.chunks.append(chunk)
+
+    def on_request_done(self):
+        environ = WSGIContainer._BASE_ENVIRON.copy()
+        parsed = urlparse(self.path)
+
+        environ['wsgi.input'] = io.BytesIO(b''.join(self.chunks))
+
+        for name, value in self.headers:
+            mangled_name = b'HTTP_' + name.replace(b'-', b'_').upper()
+            environ[_dance_decode(mangled_name)] = _dance_decode(value)
+
+        environ.update(dict(
+            REQUEST_METHOD=_dance_decode(self.method),
+            # TODO SCRIPT_NAME? like APPLICATION_ROOT in Flask...
+            PATH_INFO=_dance_decode(parsed.path),
+            QUERY_STRING=_dance_decode(parsed.query),
+            CONTENT_TYPE=environ.get('HTTP_CONTENT_TYPE', ''),
+            CONTENT_LENGTH=environ.get('HTTP_CONTENT_LENGTH', ''),
+            SERVER_NAME=_dance_decode(self.host),
+            SERVER_PORT='', # FIXME probably requires changes in nghttp2
+            SERVER_PROTOCOL='HTTP/2.0',
+        ))
+
+        response_status = [None]
+        response_headers = [None]
+        response_chunks = []
+
+        def start_response(status, headers, exc_info=None):
+            if response_status[0] is not None:
+                raise AssertionError('Response already started')
+            exc_info = None # avoid dangling circular ref - TODO is this necessary? borrowed from snippet in WSGI spec
+
+            response_status[0] = status
+            response_headers[0] = headers
+            # TODO handle exc_info
+
+            return lambda chunk: response_chunks.append(chunk)
+
+        # TODO technically, this breaks the WSGI spec by buffering the status,
+        # headers, and body until all are completely output from the app before
+        # writing the response, but it looks like nghttp2 doesn't support any
+        # other way for now
+
+        # TODO disallow yielding/returning before start_response is called
+        response_chunks.extend(self.app(environ, start_response))
+        response_body = b''.join(response_chunks)
+
+        # TODO automatically set content-length if not provided
+        self.send_response(
+            status=response_status[0],
+            headers=response_headers[0],
+            body=response_body,
+        )
+
+def wsgi_app(app):
+    return lambda *args, **kwargs: WSGIContainer(app, *args, **kwargs)
+
+
+if __name__ == '__main__':
+    import ssl
+    from werkzeug.testapp import test_app
+
+    ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+    ssl_ctx.options = ssl.OP_ALL | ssl.OP_NO_SSLv2
+    ssl_ctx.load_cert_chain('server.crt', 'server.key')
+
+    server = nghttp2.HTTP2Server(('127.0.0.1', 8443), wsgi_app(test_app),
+                                 ssl=ssl_ctx)
+    server.serve_forever()
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644 (file)
index 0000000..c108c3d
--- /dev/null
@@ -0,0 +1,12 @@
+nghttp
+nghttpd
+nghttpx
+nghttpx-unittest
+nghttpx-unittest.log
+nghttpx-unittest.trs
+test-suite.log
+.dirstamp
+libnghttpx.a
+deflatehd
+inflatehd
+h2load
diff --git a/src/HtmlParser.cc b/src/HtmlParser.cc
new file mode 100644 (file)
index 0000000..8b5f409
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "HtmlParser.h"
+
+#include <libxml/uri.h>
+
+#include "util.h"
+
+namespace nghttp2 {
+
+ParserData::ParserData(const std::string &base_uri) : base_uri(base_uri) {}
+
+HtmlParser::HtmlParser(const std::string &base_uri)
+    : base_uri_(base_uri), parser_ctx_(nullptr), parser_data_(base_uri) {}
+
+HtmlParser::~HtmlParser() { htmlFreeParserCtxt(parser_ctx_); }
+
+namespace {
+const char *get_attr(const xmlChar **attrs, const char *name) {
+  if (attrs == nullptr) {
+    return nullptr;
+  }
+  for (; *attrs; attrs += 2) {
+    if (util::strieq(reinterpret_cast<const char *>(attrs[0]), name)) {
+      return reinterpret_cast<const char *>(attrs[1]);
+    }
+  }
+  return nullptr;
+}
+} // namespace
+
+namespace {
+void add_link(ParserData *parser_data, const char *uri, RequestPriority pri) {
+  auto u = xmlBuildURI(
+      reinterpret_cast<const xmlChar *>(uri),
+      reinterpret_cast<const xmlChar *>(parser_data->base_uri.c_str()));
+  if (u) {
+    parser_data->links.push_back(
+        std::make_pair(reinterpret_cast<char *>(u), pri));
+    free(u);
+  }
+}
+} // namespace
+
+namespace {
+void start_element_func(void *user_data, const xmlChar *name,
+                        const xmlChar **attrs) {
+  auto parser_data = static_cast<ParserData *>(user_data);
+  if (util::strieq(reinterpret_cast<const char *>(name), "link")) {
+    auto rel_attr = get_attr(attrs, "rel");
+    auto href_attr = get_attr(attrs, "href");
+    if (!href_attr) {
+      return;
+    }
+    if (util::strieq(rel_attr, "shortcut icon")) {
+      add_link(parser_data, href_attr, REQ_PRI_LOWEST);
+    } else if (util::strieq(rel_attr, "stylesheet")) {
+      add_link(parser_data, href_attr, REQ_PRI_MEDIUM);
+    }
+  } else if (util::strieq(reinterpret_cast<const char *>(name), "img")) {
+    auto src_attr = get_attr(attrs, "src");
+    if (!src_attr) {
+      return;
+    }
+    add_link(parser_data, src_attr, REQ_PRI_LOWEST);
+  } else if (util::strieq(reinterpret_cast<const char *>(name), "script")) {
+    auto src_attr = get_attr(attrs, "src");
+    if (!src_attr) {
+      return;
+    }
+    add_link(parser_data, src_attr, REQ_PRI_LOW);
+  }
+}
+} // namespace
+
+namespace {
+xmlSAXHandler saxHandler = {
+    nullptr,             // internalSubsetSAXFunc
+    nullptr,             // isStandaloneSAXFunc
+    nullptr,             // hasInternalSubsetSAXFunc
+    nullptr,             // hasExternalSubsetSAXFunc
+    nullptr,             // resolveEntitySAXFunc
+    nullptr,             // getEntitySAXFunc
+    nullptr,             // entityDeclSAXFunc
+    nullptr,             // notationDeclSAXFunc
+    nullptr,             // attributeDeclSAXFunc
+    nullptr,             // elementDeclSAXFunc
+    nullptr,             // unparsedEntityDeclSAXFunc
+    nullptr,             // setDocumentLocatorSAXFunc
+    nullptr,             // startDocumentSAXFunc
+    nullptr,             // endDocumentSAXFunc
+    &start_element_func, // startElementSAXFunc
+    nullptr,             // endElementSAXFunc
+    nullptr,             // referenceSAXFunc
+    nullptr,             // charactersSAXFunc
+    nullptr,             // ignorableWhitespaceSAXFunc
+    nullptr,             // processingInstructionSAXFunc
+    nullptr,             // commentSAXFunc
+    nullptr,             // warningSAXFunc
+    nullptr,             // errorSAXFunc
+    nullptr,             // fatalErrorSAXFunc
+    nullptr,             // getParameterEntitySAXFunc
+    nullptr,             // cdataBlockSAXFunc
+    nullptr,             // externalSubsetSAXFunc
+    0,                   // unsigned int initialized
+    nullptr,             // void * _private
+    nullptr,             // startElementNsSAX2Func
+    nullptr,             // endElementNsSAX2Func
+    nullptr,             // xmlStructuredErrorFunc
+};
+} // namespace
+
+int HtmlParser::parse_chunk(const char *chunk, size_t size, int fin) {
+  if (!parser_ctx_) {
+    parser_ctx_ =
+        htmlCreatePushParserCtxt(&saxHandler, &parser_data_, chunk, size,
+                                 base_uri_.c_str(), XML_CHAR_ENCODING_NONE);
+    if (!parser_ctx_) {
+      return -1;
+    } else {
+      if (fin) {
+        return parse_chunk_internal(nullptr, 0, fin);
+      } else {
+        return 0;
+      }
+    }
+  } else {
+    return parse_chunk_internal(chunk, size, fin);
+  }
+}
+
+int HtmlParser::parse_chunk_internal(const char *chunk, size_t size, int fin) {
+  int rv = htmlParseChunk(parser_ctx_, chunk, size, fin);
+  if (rv == 0) {
+    return 0;
+  } else {
+    return -1;
+  }
+}
+
+const std::vector<std::pair<std::string, RequestPriority>> &
+HtmlParser::get_links() const {
+  return parser_data_.links;
+}
+
+void HtmlParser::clear_links() { parser_data_.links.clear(); }
+
+} // namespace nghttp2
diff --git a/src/HtmlParser.h b/src/HtmlParser.h
new file mode 100644 (file)
index 0000000..bee87d3
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTML_PARSER_H
+#define HTML_PARSER_H
+
+#include "nghttp2_config.h"
+
+#include <vector>
+#include <string>
+
+#ifdef HAVE_LIBXML2
+
+#include <libxml/HTMLparser.h>
+
+#endif // HAVE_LIBXML2
+
+namespace nghttp2 {
+
+enum RequestPriority {
+  REQ_PRI_HIGH = 0,
+  REQ_PRI_MEDIUM = 1,
+  REQ_PRI_LOW = 2,
+  REQ_PRI_LOWEST = 3
+};
+
+struct ParserData {
+  std::string base_uri;
+  std::vector<std::pair<std::string, RequestPriority>> links;
+  ParserData(const std::string &base_uri);
+};
+
+#ifdef HAVE_LIBXML2
+
+class HtmlParser {
+public:
+  HtmlParser(const std::string &base_uri);
+  ~HtmlParser();
+  int parse_chunk(const char *chunk, size_t size, int fin);
+  const std::vector<std::pair<std::string, RequestPriority>> &get_links() const;
+  void clear_links();
+
+private:
+  int parse_chunk_internal(const char *chunk, size_t size, int fin);
+
+  std::string base_uri_;
+  htmlParserCtxtPtr parser_ctx_;
+  ParserData parser_data_;
+};
+
+#else // !HAVE_LIBXML2
+
+class HtmlParser {
+public:
+  HtmlParser(const std::string &base_uri) {}
+  int parse_chunk(const char *chunk, size_t size, int fin) { return 0; }
+  const std::vector<std::pair<std::string, RequestPriority>> &
+  get_links() const {
+    return links_;
+  }
+  void clear_links() {}
+
+private:
+  std::vector<std::pair<std::string, RequestPriority>> links_;
+};
+
+#endif // !HAVE_LIBXML2
+
+} // namespace nghttp2
+
+#endif // HTML_PARSER_H
diff --git a/src/HttpServer.cc b/src/HttpServer.cc
new file mode 100644 (file)
index 0000000..d3e5c55
--- /dev/null
@@ -0,0 +1,1680 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "HttpServer.h"
+
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <cassert>
+#include <set>
+#include <iostream>
+#include <thread>
+#include <mutex>
+#include <deque>
+
+#include <openssl/err.h>
+
+#include <zlib.h>
+
+#include "app_helper.h"
+#include "http2.h"
+#include "util.h"
+#include "ssl.h"
+#include "template.h"
+
+#ifndef O_BINARY
+#define O_BINARY (0)
+#endif // O_BINARY
+
+namespace nghttp2 {
+
+namespace {
+const std::string STATUS_200 = "200";
+const std::string STATUS_301 = "301";
+const std::string STATUS_304 = "304";
+const std::string STATUS_400 = "400";
+const std::string STATUS_404 = "404";
+const std::string DEFAULT_HTML = "index.html";
+const std::string NGHTTPD_SERVER = "nghttpd nghttp2/" NGHTTP2_VERSION;
+} // namespace
+
+namespace {
+void delete_handler(Http2Handler *handler) {
+  handler->remove_self();
+  delete handler;
+}
+} // namespace
+
+namespace {
+void print_session_id(int64_t id) { std::cout << "[id=" << id << "] "; }
+} // namespace
+
+namespace {
+template <typename Array> void append_nv(Stream *stream, const Array &nva) {
+  for (size_t i = 0; i < nva.size(); ++i) {
+    auto &nv = nva[i];
+    auto token = http2::lookup_token(nv.name, nv.namelen);
+    if (token != -1) {
+      http2::index_header(stream->hdidx, token, i);
+    }
+    http2::add_header(stream->headers, nv.name, nv.namelen, nv.value,
+                      nv.valuelen, nv.flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+  }
+}
+} // namespace
+
+Config::Config()
+    : stream_read_timeout(60.), stream_write_timeout(60.),
+      session_option(nullptr), data_ptr(nullptr), padding(0), num_worker(1),
+      header_table_size(-1), port(0), verbose(false), daemon(false),
+      verify_client(false), no_tls(false), error_gzip(false),
+      early_response(false) {
+  nghttp2_option_new(&session_option);
+  nghttp2_option_set_recv_client_preface(session_option, 1);
+}
+
+Config::~Config() { nghttp2_option_del(session_option); }
+
+namespace {
+void stream_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  int rv;
+  auto stream = static_cast<Stream *>(w->data);
+  auto hd = stream->handler;
+  auto config = hd->get_config();
+
+  ev_timer_stop(hd->get_loop(), &stream->rtimer);
+  ev_timer_stop(hd->get_loop(), &stream->wtimer);
+
+  if (config->verbose) {
+    print_session_id(hd->session_id());
+    print_timer();
+    std::cout << " timeout stream_id=" << stream->stream_id << std::endl;
+  }
+
+  hd->submit_rst_stream(stream, NGHTTP2_INTERNAL_ERROR);
+
+  rv = hd->on_write();
+  if (rv == -1) {
+    delete_handler(hd);
+  }
+}
+} // namespace
+
+namespace {
+void add_stream_read_timeout(Stream *stream) {
+  auto hd = stream->handler;
+  ev_timer_again(hd->get_loop(), &stream->rtimer);
+}
+} // namespace
+
+namespace {
+void add_stream_read_timeout_if_pending(Stream *stream) {
+  auto hd = stream->handler;
+  if (ev_is_active(&stream->rtimer)) {
+    ev_timer_again(hd->get_loop(), &stream->rtimer);
+  }
+}
+} // namespace
+
+namespace {
+void add_stream_write_timeout(Stream *stream) {
+  auto hd = stream->handler;
+  ev_timer_again(hd->get_loop(), &stream->wtimer);
+}
+} // namespace
+
+namespace {
+void remove_stream_read_timeout(Stream *stream) {
+  auto hd = stream->handler;
+  ev_timer_stop(hd->get_loop(), &stream->rtimer);
+}
+} // namespace
+
+namespace {
+void remove_stream_write_timeout(Stream *stream) {
+  auto hd = stream->handler;
+  ev_timer_stop(hd->get_loop(), &stream->wtimer);
+}
+} // namespace
+
+namespace {
+void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config);
+} // namespace
+
+class Sessions {
+public:
+  Sessions(struct ev_loop *loop, const Config *config, SSL_CTX *ssl_ctx)
+      : loop_(loop), config_(config), ssl_ctx_(ssl_ctx), callbacks_(nullptr),
+        next_session_id_(1) {
+    nghttp2_session_callbacks_new(&callbacks_);
+
+    fill_callback(callbacks_, config_);
+  }
+  ~Sessions() {
+    for (auto handler : handlers_) {
+      delete handler;
+    }
+    nghttp2_session_callbacks_del(callbacks_);
+  }
+  void add_handler(Http2Handler *handler) { handlers_.insert(handler); }
+  void remove_handler(Http2Handler *handler) { handlers_.erase(handler); }
+  SSL_CTX *get_ssl_ctx() const { return ssl_ctx_; }
+  SSL *ssl_session_new(int fd) {
+    SSL *ssl = SSL_new(ssl_ctx_);
+    if (!ssl) {
+      std::cerr << "SSL_new() failed" << std::endl;
+      return nullptr;
+    }
+    if (SSL_set_fd(ssl, fd) == 0) {
+      std::cerr << "SSL_set_fd() failed" << std::endl;
+      SSL_free(ssl);
+      return nullptr;
+    }
+    return ssl;
+  }
+  const Config *get_config() const { return config_; }
+  struct ev_loop *get_loop() const {
+    return loop_;
+  }
+  int64_t get_next_session_id() {
+    auto session_id = next_session_id_;
+    if (next_session_id_ == std::numeric_limits<int64_t>::max()) {
+      next_session_id_ = 1;
+    } else {
+      ++next_session_id_;
+    }
+    return session_id;
+  }
+  const nghttp2_session_callbacks *get_callbacks() const { return callbacks_; }
+  void accept_connection(int fd) {
+    util::make_socket_nodelay(fd);
+    SSL *ssl = nullptr;
+    if (ssl_ctx_) {
+      ssl = ssl_session_new(fd);
+      if (!ssl) {
+        close(fd);
+        return;
+      }
+    }
+    auto handler =
+        make_unique<Http2Handler>(this, fd, ssl, get_next_session_id());
+    handler->setup_bev();
+    if (!ssl) {
+      if (handler->on_connect() != 0) {
+        return;
+      }
+    }
+    add_handler(handler.release());
+  }
+  void update_cached_date() { cached_date_ = util::http_date(time(nullptr)); }
+  const std::string &get_cached_date() const { return cached_date_; }
+
+private:
+  std::set<Http2Handler *> handlers_;
+  std::string cached_date_;
+  struct ev_loop *loop_;
+  const Config *config_;
+  SSL_CTX *ssl_ctx_;
+  nghttp2_session_callbacks *callbacks_;
+  int64_t next_session_id_;
+};
+
+Stream::Stream(Http2Handler *handler, int32_t stream_id)
+    : handler(handler), body_left(0), upload_left(-1), stream_id(stream_id),
+      file(-1) {
+  auto config = handler->get_config();
+  ev_timer_init(&rtimer, stream_timeout_cb, 0., config->stream_read_timeout);
+  ev_timer_init(&wtimer, stream_timeout_cb, 0., config->stream_write_timeout);
+  rtimer.data = this;
+  wtimer.data = this;
+
+  headers.reserve(10);
+
+  http2::init_hdidx(hdidx);
+}
+
+Stream::~Stream() {
+  if (file != -1) {
+    close(file);
+  }
+
+  auto loop = handler->get_loop();
+  ev_timer_stop(loop, &rtimer);
+  ev_timer_stop(loop, &wtimer);
+}
+
+namespace {
+void on_session_closed(Http2Handler *hd, int64_t session_id) {
+  if (hd->get_config()->verbose) {
+    print_session_id(session_id);
+    print_timer();
+    std::cout << " closed" << std::endl;
+  }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto hd = static_cast<Http2Handler *>(w->data);
+  hd->terminate_session(NGHTTP2_SETTINGS_TIMEOUT);
+  hd->on_write();
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  int rv;
+  auto handler = static_cast<Http2Handler *>(w->data);
+
+  rv = handler->on_read();
+  if (rv == -1) {
+    delete_handler(handler);
+  }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+  int rv;
+  auto handler = static_cast<Http2Handler *>(w->data);
+
+  rv = handler->on_write();
+  if (rv == -1) {
+    delete_handler(handler);
+  }
+}
+} // namespace
+
+Http2Handler::Http2Handler(Sessions *sessions, int fd, SSL *ssl,
+                           int64_t session_id)
+    : session_id_(session_id), session_(nullptr), sessions_(sessions),
+      ssl_(ssl), data_pending_(nullptr), data_pendinglen_(0), fd_(fd) {
+  ev_timer_init(&settings_timerev_, settings_timeout_cb, 10., 0.);
+  ev_io_init(&wev_, writecb, fd, EV_WRITE);
+  ev_io_init(&rev_, readcb, fd, EV_READ);
+
+  settings_timerev_.data = this;
+  wev_.data = this;
+  rev_.data = this;
+
+  auto loop = sessions_->get_loop();
+  ev_io_start(loop, &rev_);
+
+  if (ssl) {
+    SSL_set_accept_state(ssl);
+    read_ = &Http2Handler::tls_handshake;
+    write_ = &Http2Handler::tls_handshake;
+  } else {
+    read_ = &Http2Handler::read_clear;
+    write_ = &Http2Handler::write_clear;
+  }
+}
+
+Http2Handler::~Http2Handler() {
+  on_session_closed(this, session_id_);
+  nghttp2_session_del(session_);
+  if (ssl_) {
+    SSL_set_shutdown(ssl_, SSL_RECEIVED_SHUTDOWN);
+    ERR_clear_error();
+    SSL_shutdown(ssl_);
+  }
+  auto loop = sessions_->get_loop();
+  ev_timer_stop(loop, &settings_timerev_);
+  ev_io_stop(loop, &rev_);
+  ev_io_stop(loop, &wev_);
+  if (ssl_) {
+    SSL_free(ssl_);
+  }
+  shutdown(fd_, SHUT_WR);
+  close(fd_);
+}
+
+void Http2Handler::remove_self() { sessions_->remove_handler(this); }
+
+struct ev_loop *Http2Handler::get_loop() const {
+  return sessions_->get_loop();
+}
+
+int Http2Handler::setup_bev() { return 0; }
+
+int Http2Handler::fill_wb() {
+  if (data_pending_) {
+    auto n = std::min(wb_.wleft(), data_pendinglen_);
+    wb_.write(data_pending_, n);
+    if (n < data_pendinglen_) {
+      data_pending_ += n;
+      data_pendinglen_ -= n;
+      return 0;
+    }
+
+    data_pending_ = nullptr;
+    data_pendinglen_ = 0;
+  }
+
+  for (;;) {
+    const uint8_t *data;
+    auto datalen = nghttp2_session_mem_send(session_, &data);
+
+    if (datalen < 0) {
+      std::cerr << "nghttp2_session_mem_send() returned error: "
+                << nghttp2_strerror(datalen) << std::endl;
+      return -1;
+    }
+    if (datalen == 0) {
+      break;
+    }
+    auto n = wb_.write(data, datalen);
+    if (n < static_cast<decltype(n)>(datalen)) {
+      data_pending_ = data + n;
+      data_pendinglen_ = datalen - n;
+      break;
+    }
+  }
+  return 0;
+}
+
+int Http2Handler::read_clear() {
+  int rv;
+  std::array<uint8_t, 8192> buf;
+
+  for (;;) {
+    ssize_t nread;
+    while ((nread = read(fd_, buf.data(), buf.size())) == -1 && errno == EINTR)
+      ;
+    if (nread == -1) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        break;
+      }
+      return -1;
+    }
+    if (nread == 0) {
+      return -1;
+    }
+    rv = nghttp2_session_mem_recv(session_, buf.data(), nread);
+    if (rv < 0) {
+      if (rv != NGHTTP2_ERR_BAD_PREFACE) {
+        std::cerr << "nghttp2_session_mem_recv() returned error: "
+                  << nghttp2_strerror(rv) << std::endl;
+      }
+      return -1;
+    }
+  }
+
+  return write_(*this);
+}
+
+int Http2Handler::write_clear() {
+  auto loop = sessions_->get_loop();
+  for (;;) {
+    if (wb_.rleft() > 0) {
+      ssize_t nwrite;
+      while ((nwrite = write(fd_, wb_.pos, wb_.rleft())) == -1 &&
+             errno == EINTR)
+        ;
+      if (nwrite == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          ev_io_start(loop, &wev_);
+          return 0;
+        }
+        return -1;
+      }
+      wb_.drain(nwrite);
+      continue;
+    }
+    wb_.reset();
+    if (fill_wb() != 0) {
+      return -1;
+    }
+    if (wb_.rleft() == 0) {
+      break;
+    }
+  }
+
+  if (wb_.rleft() == 0) {
+    ev_io_stop(loop, &wev_);
+  } else {
+    ev_io_start(loop, &wev_);
+  }
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int Http2Handler::tls_handshake() {
+  ev_io_stop(sessions_->get_loop(), &wev_);
+
+  ERR_clear_error();
+
+  auto rv = SSL_do_handshake(ssl_);
+
+  if (rv == 0) {
+    return -1;
+  }
+
+  if (rv < 0) {
+    auto err = SSL_get_error(ssl_, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      return 0;
+    case SSL_ERROR_WANT_WRITE:
+      ev_io_start(sessions_->get_loop(), &wev_);
+      return 0;
+    default:
+      return -1;
+    }
+  }
+
+  if (sessions_->get_config()->verbose) {
+    std::cerr << "SSL/TLS handshake completed" << std::endl;
+  }
+
+  if (verify_npn_result() != 0) {
+    return -1;
+  }
+
+  read_ = &Http2Handler::read_tls;
+  write_ = &Http2Handler::write_tls;
+
+  if (on_connect() != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int Http2Handler::read_tls() {
+  std::array<uint8_t, 8192> buf;
+
+  ERR_clear_error();
+
+  for (;;) {
+    auto rv = SSL_read(ssl_, buf.data(), buf.size());
+
+    if (rv == 0) {
+      return -1;
+    }
+
+    if (rv < 0) {
+      auto err = SSL_get_error(ssl_, rv);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+        goto fin;
+      case SSL_ERROR_WANT_WRITE:
+        // renegotiation started
+        return -1;
+      default:
+        return -1;
+      }
+    }
+
+    auto nread = rv;
+    rv = nghttp2_session_mem_recv(session_, buf.data(), nread);
+    if (rv < 0) {
+      if (rv != NGHTTP2_ERR_BAD_PREFACE) {
+        std::cerr << "nghttp2_session_mem_recv() returned error: "
+                  << nghttp2_strerror(rv) << std::endl;
+      }
+      return -1;
+    }
+  }
+
+fin:
+  return write_(*this);
+}
+
+int Http2Handler::write_tls() {
+  auto loop = sessions_->get_loop();
+
+  ERR_clear_error();
+
+  for (;;) {
+    if (wb_.rleft() > 0) {
+      auto rv = SSL_write(ssl_, wb_.pos, wb_.rleft());
+
+      if (rv == 0) {
+        return -1;
+      }
+
+      if (rv < 0) {
+        auto err = SSL_get_error(ssl_, rv);
+        switch (err) {
+        case SSL_ERROR_WANT_READ:
+          // renegotiation started
+          return -1;
+        case SSL_ERROR_WANT_WRITE:
+          ev_io_start(sessions_->get_loop(), &wev_);
+          return 0;
+        default:
+          return -1;
+        }
+      }
+
+      wb_.drain(rv);
+      continue;
+    }
+    wb_.reset();
+    if (fill_wb() != 0) {
+      return -1;
+    }
+    if (wb_.rleft() == 0) {
+      break;
+    }
+  }
+
+  if (wb_.rleft() == 0) {
+    ev_io_stop(loop, &wev_);
+  } else {
+    ev_io_start(loop, &wev_);
+  }
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int Http2Handler::on_read() { return read_(*this); }
+
+int Http2Handler::on_write() { return write_(*this); }
+
+int Http2Handler::on_connect() {
+  int r;
+
+  r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this,
+                                  sessions_->get_config()->session_option);
+  if (r != 0) {
+    return r;
+  }
+  std::array<nghttp2_settings_entry, 4> entry;
+  size_t niv = 1;
+
+  entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  entry[0].value = 100;
+
+  if (sessions_->get_config()->header_table_size >= 0) {
+    entry[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+    entry[niv].value = sessions_->get_config()->header_table_size;
+    ++niv;
+  }
+  r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
+  if (r != 0) {
+    return r;
+  }
+
+  ev_timer_start(sessions_->get_loop(), &settings_timerev_);
+
+  return on_write();
+}
+
+int Http2Handler::verify_npn_result() {
+  const unsigned char *next_proto = nullptr;
+  unsigned int next_proto_len;
+  // Check the negotiated protocol in NPN or ALPN
+  SSL_get0_next_proto_negotiated(ssl_, &next_proto, &next_proto_len);
+  for (int i = 0; i < 2; ++i) {
+    if (next_proto) {
+      if (sessions_->get_config()->verbose) {
+        std::string proto(next_proto, next_proto + next_proto_len);
+        std::cout << "The negotiated protocol: " << proto << std::endl;
+      }
+      if (util::check_h2_is_selected(next_proto, next_proto_len)) {
+        return 0;
+      }
+      break;
+    } else {
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+      SSL_get0_alpn_selected(ssl_, &next_proto, &next_proto_len);
+#else  // OPENSSL_VERSION_NUMBER < 0x10002000L
+      break;
+#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
+    }
+  }
+  if (sessions_->get_config()->verbose) {
+    std::cerr << "Client did not advertise HTTP/2 protocol."
+              << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
+              << std::endl;
+  }
+  return -1;
+}
+
+int Http2Handler::submit_file_response(const std::string &status,
+                                       Stream *stream, time_t last_modified,
+                                       off_t file_length,
+                                       nghttp2_data_provider *data_prd) {
+  std::string content_length = util::utos(file_length);
+  std::string last_modified_str;
+  auto nva = make_array(http2::make_nv_ls(":status", status),
+                        http2::make_nv_ls("server", NGHTTPD_SERVER),
+                        http2::make_nv_ls("content-length", content_length),
+                        http2::make_nv_ll("cache-control", "max-age=3600"),
+                        http2::make_nv_ls("date", sessions_->get_cached_date()),
+                        http2::make_nv_ll("", ""));
+  size_t nvlen = 5;
+  if (last_modified != 0) {
+    last_modified_str = util::http_date(last_modified);
+    nva[nvlen++] = http2::make_nv_ls("last-modified", last_modified_str);
+  }
+  return nghttp2_submit_response(session_, stream->stream_id, nva.data(), nvlen,
+                                 data_prd);
+}
+
+int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
+                                  const Headers &headers,
+                                  nghttp2_data_provider *data_prd) {
+  auto nva = std::vector<nghttp2_nv>();
+  nva.reserve(3 + headers.size());
+  nva.push_back(http2::make_nv_ls(":status", status));
+  nva.push_back(http2::make_nv_ls("server", NGHTTPD_SERVER));
+  nva.push_back(http2::make_nv_ls("date", sessions_->get_cached_date()));
+  for (auto &nv : headers) {
+    nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
+  }
+  int r = nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
+                                  data_prd);
+  return r;
+}
+
+int Http2Handler::submit_response(const std::string &status, int32_t stream_id,
+                                  nghttp2_data_provider *data_prd) {
+  auto nva = make_array(http2::make_nv_ls(":status", status),
+                        http2::make_nv_ls("server", NGHTTPD_SERVER));
+  return nghttp2_submit_response(session_, stream_id, nva.data(), nva.size(),
+                                 data_prd);
+}
+
+int Http2Handler::submit_non_final_response(const std::string &status,
+                                            int32_t stream_id) {
+  auto nva = make_array(http2::make_nv_ls(":status", status));
+  return nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE, stream_id, nullptr,
+                                nva.data(), nva.size(), nullptr);
+}
+
+int Http2Handler::submit_push_promise(Stream *stream,
+                                      const std::string &push_path) {
+  auto authority =
+      http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
+
+  if (!authority) {
+    authority =
+        http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers);
+  }
+
+  auto nva =
+      make_array(http2::make_nv_ll(":method", "GET"),
+                 http2::make_nv_ls(":path", push_path),
+                 get_config()->no_tls ? http2::make_nv_ll(":scheme", "http")
+                                      : http2::make_nv_ll(":scheme", "https"),
+                 http2::make_nv_ls(":authority", authority->value));
+
+  auto promised_stream_id = nghttp2_submit_push_promise(
+      session_, NGHTTP2_FLAG_END_HEADERS, stream->stream_id, nva.data(),
+      nva.size(), nullptr);
+
+  if (promised_stream_id < 0) {
+    return promised_stream_id;
+  }
+
+  auto promised_stream = make_unique<Stream>(this, promised_stream_id);
+
+  append_nv(promised_stream.get(), nva);
+  add_stream(promised_stream_id, std::move(promised_stream));
+
+  return 0;
+}
+
+int Http2Handler::submit_rst_stream(Stream *stream, uint32_t error_code) {
+  remove_stream_read_timeout(stream);
+  remove_stream_write_timeout(stream);
+
+  return nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE,
+                                   stream->stream_id, error_code);
+}
+
+void Http2Handler::add_stream(int32_t stream_id,
+                              std::unique_ptr<Stream> stream) {
+  id2stream_[stream_id] = std::move(stream);
+}
+
+void Http2Handler::remove_stream(int32_t stream_id) {
+  id2stream_.erase(stream_id);
+}
+
+Stream *Http2Handler::get_stream(int32_t stream_id) {
+  auto itr = id2stream_.find(stream_id);
+  if (itr == std::end(id2stream_)) {
+    return nullptr;
+  } else {
+    return (*itr).second.get();
+  }
+}
+
+int64_t Http2Handler::session_id() const { return session_id_; }
+
+Sessions *Http2Handler::get_sessions() const { return sessions_; }
+
+const Config *Http2Handler::get_config() const {
+  return sessions_->get_config();
+}
+
+void Http2Handler::remove_settings_timer() {
+  ev_timer_stop(sessions_->get_loop(), &settings_timerev_);
+}
+
+void Http2Handler::terminate_session(uint32_t error_code) {
+  nghttp2_session_terminate_session(session_, error_code);
+}
+
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+                           uint8_t *buf, size_t length, uint32_t *data_flags,
+                           nghttp2_data_source *source, void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+  auto stream = hd->get_stream(stream_id);
+
+  int fd = source->fd;
+  ssize_t nread;
+
+  while ((nread = read(fd, buf, length)) == -1 && errno == EINTR)
+    ;
+
+  if (nread == -1) {
+    remove_stream_read_timeout(stream);
+    remove_stream_write_timeout(stream);
+
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  stream->body_left -= nread;
+  if (nread == 0 || stream->body_left <= 0) {
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+    if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
+      remove_stream_read_timeout(stream);
+      remove_stream_write_timeout(stream);
+
+      hd->submit_rst_stream(stream, NGHTTP2_NO_ERROR);
+    }
+  }
+
+  return nread;
+}
+
+namespace {
+void prepare_status_response(Stream *stream, Http2Handler *hd,
+                             const std::string &status) {
+  int pipefd[2];
+  if (status == STATUS_304 || pipe(pipefd) == -1) {
+    hd->submit_response(status, stream->stream_id, 0);
+    return;
+  }
+  std::string body;
+  body.reserve(256);
+  body = "<html><head><title>";
+  body += status;
+  body += "</title></head><body><h1>";
+  body += status;
+  body += "</h1><hr><address>";
+  body += NGHTTPD_SERVER;
+  body += " at port ";
+  body += util::utos(hd->get_config()->port);
+  body += "</address>";
+  body += "</body></html>";
+
+  Headers headers;
+  if (hd->get_config()->error_gzip) {
+    gzFile write_fd = gzdopen(pipefd[1], "w");
+    gzwrite(write_fd, body.c_str(), body.size());
+    gzclose(write_fd);
+    headers.emplace_back("content-encoding", "gzip");
+  } else {
+    ssize_t rv;
+
+    while ((rv = write(pipefd[1], body.c_str(), body.size())) == -1 &&
+           errno == EINTR)
+      ;
+
+    if (rv != static_cast<ssize_t>(body.size())) {
+      std::cerr << "Could not write all response body: " << rv << std::endl;
+    }
+  }
+  close(pipefd[1]);
+
+  stream->file = pipefd[0];
+  stream->body_left = body.size();
+  nghttp2_data_provider data_prd;
+  data_prd.source.fd = pipefd[0];
+  data_prd.read_callback = file_read_callback;
+  headers.emplace_back("content-type", "text/html; charset=UTF-8");
+  hd->submit_response(status, stream->stream_id, headers, &data_prd);
+}
+} // namespace
+
+namespace {
+void prepare_redirect_response(Stream *stream, Http2Handler *hd,
+                               const std::string &path,
+                               const std::string &status) {
+  auto scheme =
+      http2::get_header(stream->hdidx, http2::HD__SCHEME, stream->headers);
+  auto authority =
+      http2::get_header(stream->hdidx, http2::HD__AUTHORITY, stream->headers);
+  if (!authority) {
+    authority =
+        http2::get_header(stream->hdidx, http2::HD_HOST, stream->headers);
+  }
+
+  auto redirect_url = scheme->value;
+  redirect_url += "://";
+  redirect_url += authority->value;
+  redirect_url += path;
+
+  auto headers = Headers{{"location", redirect_url}};
+
+  hd->submit_response(status, stream->stream_id, headers, nullptr);
+}
+} // namespace
+
+namespace {
+void prepare_response(Stream *stream, Http2Handler *hd,
+                      bool allow_push = true) {
+  int rv;
+  auto reqpath =
+      http2::get_header(stream->hdidx, http2::HD__PATH, stream->headers)->value;
+  auto ims =
+      get_header(stream->hdidx, http2::HD_IF_MODIFIED_SINCE, stream->headers);
+
+  time_t last_mod = 0;
+  bool last_mod_found = false;
+  if (ims) {
+    last_mod_found = true;
+    last_mod = util::parse_http_date(ims->value);
+  }
+  auto query_pos = reqpath.find("?");
+  std::string url;
+  if (query_pos != std::string::npos) {
+    // Do not response to this request to allow clients to test timeouts.
+    if (reqpath.find("nghttpd_do_not_respond_to_req=yes", query_pos) !=
+        std::string::npos) {
+      return;
+    }
+    url = reqpath.substr(0, query_pos);
+  } else {
+    url = reqpath;
+  }
+
+  url = util::percentDecode(url.begin(), url.end());
+  if (!util::check_path(url)) {
+    prepare_status_response(stream, hd, STATUS_404);
+    return;
+  }
+  auto push_itr = hd->get_config()->push.find(url);
+  if (allow_push && push_itr != std::end(hd->get_config()->push)) {
+    for (auto &push_path : (*push_itr).second) {
+      rv = hd->submit_push_promise(stream, push_path);
+      if (rv != 0) {
+        std::cerr << "nghttp2_submit_push_promise() returned error: "
+                  << nghttp2_strerror(rv) << std::endl;
+      }
+    }
+  }
+  std::string path = hd->get_config()->htdocs + url;
+  if (path[path.size() - 1] == '/') {
+    path += DEFAULT_HTML;
+  }
+  int file = open(path.c_str(), O_RDONLY | O_BINARY);
+  if (file == -1) {
+    prepare_status_response(stream, hd, STATUS_404);
+
+    return;
+  }
+
+  struct stat buf;
+
+  if (fstat(file, &buf) == -1) {
+    close(file);
+    prepare_status_response(stream, hd, STATUS_404);
+
+    return;
+  }
+
+  if (buf.st_mode & S_IFDIR) {
+    close(file);
+
+    if (query_pos == std::string::npos) {
+      reqpath += "/";
+    } else {
+      reqpath.insert(query_pos, "/");
+    }
+
+    prepare_redirect_response(stream, hd, reqpath, STATUS_301);
+
+    return;
+  }
+
+  stream->file = file;
+  stream->body_left = buf.st_size;
+
+  nghttp2_data_provider data_prd;
+
+  data_prd.source.fd = file;
+  data_prd.read_callback = file_read_callback;
+
+  if (last_mod_found && buf.st_mtime <= last_mod) {
+    prepare_status_response(stream, hd, STATUS_304);
+
+    return;
+  }
+
+  hd->submit_file_response(STATUS_200, stream, buf.st_mtime, buf.st_size,
+                           &data_prd);
+}
+} // namespace
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                       const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen, uint8_t flags,
+                       void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+  if (hd->get_config()->verbose) {
+    print_session_id(hd->session_id());
+    verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
+                               flags, user_data);
+  }
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+  auto stream = hd->get_stream(frame->hd.stream_id);
+  if (!stream) {
+    return 0;
+  }
+
+  if (!http2::check_nv(name, namelen, value, valuelen)) {
+    hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  auto token = http2::lookup_token(name, namelen);
+
+  if (name[0] == ':') {
+    if ((!stream->headers.empty() &&
+         stream->headers.back().name.c_str()[0] != ':') ||
+        !http2::check_http2_request_pseudo_header(stream->hdidx, token)) {
+
+      hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+  }
+
+  if (!http2::http2_header_allowed(token)) {
+    hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  switch (token) {
+  case http2::HD_CONTENT_LENGTH: {
+    auto upload_left = util::parse_uint(value, valuelen);
+    if (upload_left != -1) {
+      stream->upload_left = upload_left;
+    }
+    break;
+  }
+  case http2::HD_TE:
+    if (!util::strieq("trailers", value, valuelen)) {
+      hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    break;
+  }
+
+  http2::index_header(stream->hdidx, token, stream->headers.size());
+  http2::add_header(stream->headers, name, namelen, value, valuelen,
+                    flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  auto stream = make_unique<Stream>(hd, frame->hd.stream_id);
+
+  add_stream_read_timeout(stream.get());
+
+  hd->add_stream(frame->hd.stream_id, std::move(stream));
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int hd_on_frame_recv_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+  if (hd->get_config()->verbose) {
+    print_session_id(hd->session_id());
+    verbose_on_frame_recv_callback(session, frame, user_data);
+  }
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA: {
+    // TODO Handle POST
+    auto stream = hd->get_stream(frame->hd.stream_id);
+    if (!stream) {
+      return 0;
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      remove_stream_read_timeout(stream);
+      if (stream->upload_left > 0) {
+        hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+        return 0;
+      }
+      if (!hd->get_config()->early_response) {
+        prepare_response(stream, hd);
+      }
+    } else {
+      add_stream_read_timeout(stream);
+    }
+
+    break;
+  }
+  case NGHTTP2_HEADERS: {
+    auto stream = hd->get_stream(frame->hd.stream_id);
+    if (!stream) {
+      return 0;
+    }
+
+    if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+
+      if (!http2::http2_mandatory_request_headers_presence(stream->hdidx)) {
+        hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+        return 0;
+      }
+
+      auto expect100 =
+          http2::get_header(stream->hdidx, http2::HD_EXPECT, stream->headers);
+
+      if (expect100 && util::strieq("100-continue", expect100->value.c_str())) {
+        hd->submit_non_final_response("100", frame->hd.stream_id);
+      }
+
+      if (hd->get_config()->early_response) {
+        prepare_response(stream, hd);
+      }
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      remove_stream_read_timeout(stream);
+      if (stream->upload_left > 0) {
+        hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+        return 0;
+      }
+      if (!hd->get_config()->early_response) {
+        prepare_response(stream, hd);
+      }
+    } else {
+      add_stream_read_timeout(stream);
+    }
+
+    break;
+  }
+  case NGHTTP2_SETTINGS:
+    if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
+      hd->remove_settings_timer();
+    }
+    break;
+  default:
+    break;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int hd_on_frame_send_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+
+  if (hd->get_config()->verbose) {
+    print_session_id(hd->session_id());
+    verbose_on_frame_send_callback(session, frame, user_data);
+  }
+
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA:
+  case NGHTTP2_HEADERS: {
+    auto stream = hd->get_stream(frame->hd.stream_id);
+
+    if (!stream) {
+      return 0;
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      remove_stream_write_timeout(stream);
+    } else if (std::min(nghttp2_session_get_stream_remote_window_size(
+                            session, frame->hd.stream_id),
+                        nghttp2_session_get_remote_window_size(session)) <= 0) {
+      // If stream is blocked by flow control, enable write timeout.
+      add_stream_read_timeout_if_pending(stream);
+      add_stream_write_timeout(stream);
+    } else {
+      add_stream_read_timeout_if_pending(stream);
+      remove_stream_write_timeout(stream);
+    }
+
+    break;
+  }
+  case NGHTTP2_PUSH_PROMISE: {
+    auto promised_stream_id = frame->push_promise.promised_stream_id;
+    auto promised_stream = hd->get_stream(promised_stream_id);
+    auto stream = hd->get_stream(frame->hd.stream_id);
+
+    if (!stream || !promised_stream) {
+      return 0;
+    }
+
+    add_stream_read_timeout_if_pending(stream);
+    add_stream_write_timeout(stream);
+
+    prepare_response(promised_stream, hd, /*allow_push */ false);
+  }
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+ssize_t select_padding_callback(nghttp2_session *session,
+                                const nghttp2_frame *frame, size_t max_payload,
+                                void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+  return std::min(max_payload, frame->hd.length + hd->get_config()->padding);
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+  auto stream = hd->get_stream(stream_id);
+
+  if (!stream) {
+    return 0;
+  }
+
+  // TODO Handle POST
+
+  if (stream->upload_left != -1) {
+    if (stream->upload_left < static_cast<int64_t>(len)) {
+      stream->upload_left = -1;
+      hd->submit_rst_stream(stream, NGHTTP2_PROTOCOL_ERROR);
+      return 0;
+    }
+    stream->upload_left -= len;
+  }
+
+  add_stream_read_timeout(stream);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                             uint32_t error_code, void *user_data) {
+  auto hd = static_cast<Http2Handler *>(user_data);
+  hd->remove_stream(stream_id);
+  if (hd->get_config()->verbose) {
+    print_session_id(hd->session_id());
+    print_timer();
+    printf(" stream_id=%d closed\n", stream_id);
+    fflush(stdout);
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(
+      callbacks, hd_on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_frame_send_callback(
+      callbacks, hd_on_frame_send_callback);
+
+  if (config->verbose) {
+    nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+        callbacks, verbose_on_invalid_frame_recv_callback);
+  }
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+
+  if (config->padding) {
+    nghttp2_session_callbacks_set_select_padding_callback(
+        callbacks, select_padding_callback);
+  }
+}
+} // namespace
+
+struct ClientInfo {
+  int fd;
+};
+
+struct Worker {
+  std::unique_ptr<Sessions> sessions;
+  ev_async w;
+  // protectes q
+  std::mutex m;
+  std::deque<ClientInfo> q;
+};
+
+namespace {
+void worker_acceptcb(struct ev_loop *loop, ev_async *w, int revents) {
+  auto worker = static_cast<Worker *>(w->data);
+  auto &sessions = worker->sessions;
+
+  std::deque<ClientInfo> q;
+  {
+    std::lock_guard<std::mutex> lock(worker->m);
+    q.swap(worker->q);
+  }
+
+  for (auto c : q) {
+    sessions->accept_connection(c.fd);
+  }
+}
+} // namespace
+
+namespace {
+void run_worker(Worker *worker) {
+  auto loop = worker->sessions->get_loop();
+  worker->sessions->update_cached_date();
+
+  ev_run(loop, 0);
+}
+} // namespace
+
+class AcceptHandler {
+public:
+  AcceptHandler(Sessions *sessions, const Config *config)
+      : sessions_(sessions), config_(config), next_worker_(0) {
+    if (config_->num_worker == 1) {
+      return;
+    }
+    for (size_t i = 0; i < config_->num_worker; ++i) {
+      if (config_->verbose) {
+        std::cerr << "spawning thread #" << i << std::endl;
+      }
+      auto worker = make_unique<Worker>();
+      auto loop = ev_loop_new(0);
+      worker->sessions =
+          make_unique<Sessions>(loop, config_, sessions_->get_ssl_ctx());
+      ev_async_init(&worker->w, worker_acceptcb);
+      worker->w.data = worker.get();
+      ev_async_start(loop, &worker->w);
+
+      auto t = std::thread(run_worker, worker.get());
+      t.detach();
+      workers_.push_back(std::move(worker));
+    }
+  }
+  void accept_connection(int fd) {
+    if (config_->num_worker == 1) {
+      sessions_->accept_connection(fd);
+      return;
+    }
+
+    // Dispatch client to the one of the worker threads, in a round
+    // robin manner.
+    auto &worker = workers_[next_worker_];
+    if (next_worker_ == config_->num_worker - 1) {
+      next_worker_ = 0;
+    } else {
+      ++next_worker_;
+    }
+    {
+      std::lock_guard<std::mutex> lock(worker->m);
+      worker->q.push_back({fd});
+    }
+    ev_async_send(worker->sessions->get_loop(), &worker->w);
+  }
+
+private:
+  std::vector<std::unique_ptr<Worker>> workers_;
+  Sessions *sessions_;
+  const Config *config_;
+  // In multi threading mode, this points to the next thread that
+  // client will be dispatched.
+  size_t next_worker_;
+};
+
+namespace {
+void acceptcb(struct ev_loop *loop, ev_io *w, int revents);
+} // namespace
+
+class ListenEventHandler {
+public:
+  ListenEventHandler(Sessions *sessions, int fd,
+                     std::shared_ptr<AcceptHandler> acceptor)
+      : acceptor_(acceptor), sessions_(sessions), fd_(fd) {
+    ev_io_init(&w_, acceptcb, fd, EV_READ);
+    w_.data = this;
+    ev_io_start(sessions_->get_loop(), &w_);
+  }
+  void accept_connection() {
+    for (;;) {
+#ifdef HAVE_ACCEPT4
+      auto fd = accept4(fd_, nullptr, nullptr, SOCK_NONBLOCK);
+#else  // !HAVE_ACCEPT4
+      auto fd = accept(fd_, nullptr, nullptr);
+#endif // !HAVE_ACCEPT4
+      if (fd == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          break;
+        }
+        continue;
+      }
+#ifndef HAVE_ACCEPT4
+      util::make_socket_nonblocking(fd);
+#endif // !HAVE_ACCEPT4
+      acceptor_->accept_connection(fd);
+    }
+  }
+
+private:
+  ev_io w_;
+  std::shared_ptr<AcceptHandler> acceptor_;
+  Sessions *sessions_;
+  int fd_;
+};
+
+namespace {
+void acceptcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto handler = static_cast<ListenEventHandler *>(w->data);
+  handler->accept_connection();
+}
+} // namespace
+
+HttpServer::HttpServer(const Config *config) : config_(config) {}
+
+namespace {
+int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
+                  void *arg) {
+  auto next_proto = static_cast<std::vector<unsigned char> *>(arg);
+  *data = next_proto->data();
+  *len = next_proto->size();
+  return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+namespace {
+int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
+  // We don't verify the client certificate. Just request it for the
+  // testing purpose.
+  return 1;
+}
+} // namespace
+
+namespace {
+int start_listen(struct ev_loop *loop, Sessions *sessions,
+                 const Config *config) {
+  addrinfo hints;
+  int r;
+  bool ok = false;
+
+  auto acceptor = std::make_shared<AcceptHandler>(sessions, config);
+  auto service = util::utos(config->port);
+
+  memset(&hints, 0, sizeof(addrinfo));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+  hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+
+  addrinfo *res, *rp;
+  r = getaddrinfo(nullptr, service.c_str(), &hints, &res);
+  if (r != 0) {
+    std::cerr << "getaddrinfo() failed: " << gai_strerror(r) << std::endl;
+    return -1;
+  }
+  for (rp = res; rp; rp = rp->ai_next) {
+    int fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    if (fd == -1) {
+      continue;
+    }
+    int val = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   static_cast<socklen_t>(sizeof(val))) == -1) {
+      close(fd);
+      continue;
+    }
+    (void)util::make_socket_nonblocking(fd);
+#ifdef IPV6_V6ONLY
+    if (rp->ai_family == AF_INET6) {
+      if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                     static_cast<socklen_t>(sizeof(val))) == -1) {
+        close(fd);
+        continue;
+      }
+    }
+#endif // IPV6_V6ONLY
+    if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 && listen(fd, 1000) == 0) {
+      new ListenEventHandler(sessions, fd, acceptor);
+
+      if (config->verbose) {
+        std::cout << (rp->ai_family == AF_INET ? "IPv4" : "IPv6")
+                  << ": listen on port " << config->port << std::endl;
+      }
+      ok = true;
+      continue;
+    } else {
+      std::cerr << strerror(errno) << std::endl;
+    }
+    close(fd);
+  }
+  freeaddrinfo(res);
+
+  if (!ok) {
+    return -1;
+  }
+  return 0;
+}
+} // namespace
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+namespace {
+int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+                         unsigned char *outlen, const unsigned char *in,
+                         unsigned int inlen, void *arg) {
+  auto config = static_cast<HttpServer *>(arg)->get_config();
+  if (config->verbose) {
+    std::cout << "[ALPN] client offers:" << std::endl;
+  }
+  if (config->verbose) {
+    for (unsigned int i = 0; i < inlen; i += in [i] + 1) {
+      std::cout << " * ";
+      std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
+      std::cout << std::endl;
+    }
+  }
+  if (!util::select_h2(out, outlen, in, inlen)) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+
+int HttpServer::run() {
+  SSL_CTX *ssl_ctx = nullptr;
+  std::vector<unsigned char> next_proto;
+
+  if (!config_->no_tls) {
+    ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+    if (!ssl_ctx) {
+      std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+      return -1;
+    }
+
+    SSL_CTX_set_options(ssl_ctx,
+                        SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                            SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+                            SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET |
+                            SSL_OP_CIPHER_SERVER_PREFERENCE);
+    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+    if (SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST) == 0) {
+      std::cerr << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+      return -1;
+    }
+
+    const unsigned char sid_ctx[] = "nghttpd";
+    SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
+    SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
+
+#ifndef OPENSSL_NO_EC
+
+    // Disabled SSL_CTX_set_ecdh_auto, because computational cost of
+    // chosen curve is much higher than P-256.
+
+    // #if OPENSSL_VERSION_NUMBER >= 0x10002000L
+    //     SSL_CTX_set_ecdh_auto(ssl_ctx, 1);
+    // #else // OPENSSL_VERSION_NUBMER < 0x10002000L
+    // Use P-256, which is sufficiently secure at the time of this
+    // writing.
+    auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+    if (ecdh == nullptr) {
+      std::cerr << "EC_KEY_new_by_curv_name failed: "
+                << ERR_error_string(ERR_get_error(), nullptr);
+      return -1;
+    }
+    SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+    EC_KEY_free(ecdh);
+// #endif // OPENSSL_VERSION_NUBMER < 0x10002000L
+
+#endif // OPENSSL_NO_EC
+
+    if (!config_->dh_param_file.empty()) {
+      // Read DH parameters from file
+      auto bio = BIO_new_file(config_->dh_param_file.c_str(), "r");
+      if (bio == nullptr) {
+        std::cerr << "BIO_new_file() failed: "
+                  << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+        return -1;
+      }
+
+      auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
+
+      if (dh == nullptr) {
+        std::cerr << "PEM_read_bio_DHparams() failed: "
+                  << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+        return -1;
+      }
+
+      SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+      DH_free(dh);
+      BIO_free(bio);
+    }
+
+    if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config_->private_key_file.c_str(),
+                                    SSL_FILETYPE_PEM) != 1) {
+      std::cerr << "SSL_CTX_use_PrivateKey_file failed." << std::endl;
+      return -1;
+    }
+    if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
+                                           config_->cert_file.c_str()) != 1) {
+      std::cerr << "SSL_CTX_use_certificate_file failed." << std::endl;
+      return -1;
+    }
+    if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
+      std::cerr << "SSL_CTX_check_private_key failed." << std::endl;
+      return -1;
+    }
+    if (config_->verify_client) {
+      SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+                                      SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                         verify_callback);
+    }
+
+    next_proto = util::get_default_alpn();
+
+    SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, &next_proto);
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+    // ALPN selection callback
+    SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, this);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+  }
+
+  auto loop = EV_DEFAULT;
+
+  Sessions sessions(loop, config_, ssl_ctx);
+  if (start_listen(loop, &sessions, config_) != 0) {
+    std::cerr << "Could not listen" << std::endl;
+    return -1;
+  }
+
+  if (config_->num_worker == 1) {
+    sessions.update_cached_date();
+  }
+
+  ev_run(loop, 0);
+  return 0;
+}
+
+const Config *HttpServer::get_config() const { return config_; }
+
+} // namespace nghttp2
diff --git a/src/HttpServer.h b/src/HttpServer.h
new file mode 100644 (file)
index 0000000..222daeb
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTTP_SERVER_H
+#define HTTP_SERVER_H
+
+#include "nghttp2_config.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <cstdlib>
+
+#include <string>
+#include <vector>
+#include <map>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http2.h"
+#include "buffer.h"
+
+namespace nghttp2 {
+
+struct Config {
+  std::map<std::string, std::vector<std::string>> push;
+  std::string htdocs;
+  std::string host;
+  std::string private_key_file;
+  std::string cert_file;
+  std::string dh_param_file;
+  ev_tstamp stream_read_timeout;
+  ev_tstamp stream_write_timeout;
+  nghttp2_option *session_option;
+  void *data_ptr;
+  size_t padding;
+  size_t num_worker;
+  ssize_t header_table_size;
+  uint16_t port;
+  bool verbose;
+  bool daemon;
+  bool verify_client;
+  bool no_tls;
+  bool error_gzip;
+  bool early_response;
+  Config();
+  ~Config();
+};
+
+class Http2Handler;
+
+struct Stream {
+  Headers headers;
+  Http2Handler *handler;
+  ev_timer rtimer;
+  ev_timer wtimer;
+  int64_t body_left;
+  int64_t upload_left;
+  int32_t stream_id;
+  int file;
+  http2::HeaderIndex hdidx;
+  Stream(Http2Handler *handler, int32_t stream_id);
+  ~Stream();
+};
+
+class Sessions;
+
+class Http2Handler {
+public:
+  Http2Handler(Sessions *sessions, int fd, SSL *ssl, int64_t session_id);
+  ~Http2Handler();
+
+  void remove_self();
+  int setup_bev();
+  int on_read();
+  int on_write();
+  int on_connect();
+  int verify_npn_result();
+
+  int submit_file_response(const std::string &status, Stream *stream,
+                           time_t last_modified, off_t file_length,
+                           nghttp2_data_provider *data_prd);
+
+  int submit_response(const std::string &status, int32_t stream_id,
+                      nghttp2_data_provider *data_prd);
+
+  int submit_response(const std::string &status, int32_t stream_id,
+                      const Headers &headers, nghttp2_data_provider *data_prd);
+
+  int submit_non_final_response(const std::string &status, int32_t stream_id);
+
+  int submit_push_promise(Stream *stream, const std::string &push_path);
+
+  int submit_rst_stream(Stream *stream, uint32_t error_code);
+
+  void add_stream(int32_t stream_id, std::unique_ptr<Stream> stream);
+  void remove_stream(int32_t stream_id);
+  Stream *get_stream(int32_t stream_id);
+  int64_t session_id() const;
+  Sessions *get_sessions() const;
+  const Config *get_config() const;
+  void remove_settings_timer();
+  void terminate_session(uint32_t error_code);
+
+  int fill_wb();
+
+  int read_clear();
+  int write_clear();
+  int tls_handshake();
+  int read_tls();
+  int write_tls();
+
+  struct ev_loop *get_loop() const;
+
+private:
+  ev_io wev_;
+  ev_io rev_;
+  ev_timer settings_timerev_;
+  std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
+  Buffer<65536> wb_;
+  std::function<int(Http2Handler &)> read_, write_;
+  int64_t session_id_;
+  nghttp2_session *session_;
+  Sessions *sessions_;
+  SSL *ssl_;
+  const uint8_t *data_pending_;
+  size_t data_pendinglen_;
+  int fd_;
+};
+
+class HttpServer {
+public:
+  HttpServer(const Config *config);
+  int listen();
+  int run();
+  const Config *get_config() const;
+
+private:
+  const Config *config_;
+};
+
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+                           uint8_t *buf, size_t length, int *eof,
+                           nghttp2_data_source *source, void *user_data);
+
+} // namespace nghttp2
+
+#endif // HTTP_SERVER_H
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..4b27432
--- /dev/null
@@ -0,0 +1,194 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS = includes
+
+bin_PROGRAMS =
+check_PROGRAMS =
+TESTS =
+
+AM_CFLAGS = $(WARNCFLAGS)
+AM_CPPFLAGS = \
+       -Wall \
+       -I$(top_srcdir)/lib/includes \
+       -I$(top_builddir)/lib/includes \
+       -I$(top_srcdir)/lib \
+       -I$(top_srcdir)/src/includes \
+       -I$(top_srcdir)/third-party \
+       @LIBSPDYLAY_CFLAGS@ \
+       @XML_CPPFLAGS@  \
+       @LIBEV_CFLAGS@ \
+       @OPENSSL_CFLAGS@ \
+       @JANSSON_CFLAGS@ \
+       @ZLIB_CFLAGS@ \
+       @DEFS@
+
+LDADD = $(top_builddir)/lib/libnghttp2.la \
+       $(top_builddir)/third-party/libhttp-parser.la \
+       @JEMALLOC_LIBS@ \
+       @LIBSPDYLAY_LIBS@ \
+       @XML_LIBS@ \
+       @LIBEV_LIBS@ \
+       @OPENSSL_LIBS@ \
+       @JANSSON_LIBS@ \
+       @ZLIB_LIBS@ \
+       @APPLDFLAGS@
+
+if ENABLE_APP
+
+bin_PROGRAMS += nghttp nghttpd nghttpx
+
+HELPER_OBJECTS = util.cc \
+       http2.cc timegm.c app_helper.cc nghttp2_gzip.c
+HELPER_HFILES = util.h \
+       http2.h timegm.h app_helper.h nghttp2_config.h \
+       nghttp2_gzip.h
+
+HTML_PARSER_OBJECTS =
+HTML_PARSER_HFILES = HtmlParser.h
+
+if HAVE_LIBXML2
+HTML_PARSER_OBJECTS += HtmlParser.cc
+endif # HAVE_LIBXML2
+
+nghttp_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttp.cc nghttp.h \
+       ${HTML_PARSER_OBJECTS} ${HTML_PARSER_HFILES} \
+       ssl.cc ssl.h
+
+nghttpd_SOURCES = ${HELPER_OBJECTS} ${HELPER_HFILES} nghttpd.cc \
+       ssl.cc ssl.h \
+       HttpServer.cc HttpServer.h
+
+bin_PROGRAMS += h2load
+
+h2load_SOURCES = util.cc util.h \
+       http2.cc http2.h h2load.cc h2load.h \
+       timegm.c timegm.h \
+       ssl.cc ssl.h \
+       h2load_session.h \
+       h2load_http2_session.cc h2load_http2_session.h
+
+if HAVE_SPDYLAY
+h2load_SOURCES += h2load_spdy_session.cc h2load_spdy_session.h
+endif # HAVE_SPDYLAY
+
+NGHTTPX_SRCS = \
+       util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
+       app_helper.cc app_helper.h \
+       ssl.cc ssl.h \
+       shrpx_config.cc shrpx_config.h \
+       shrpx_error.h \
+       shrpx_accept_handler.cc shrpx_accept_handler.h \
+       shrpx_connection_handler.cc shrpx_connection_handler.h \
+       shrpx_client_handler.cc shrpx_client_handler.h \
+       shrpx_upstream.h \
+       shrpx_http2_upstream.cc shrpx_http2_upstream.h \
+       shrpx_https_upstream.cc shrpx_https_upstream.h \
+       shrpx_downstream.cc shrpx_downstream.h \
+       shrpx_downstream_connection.cc shrpx_downstream_connection.h \
+       shrpx_http_downstream_connection.cc shrpx_http_downstream_connection.h \
+       shrpx_http2_downstream_connection.cc shrpx_http2_downstream_connection.h \
+       shrpx_http2_session.cc shrpx_http2_session.h \
+       shrpx_downstream_queue.cc shrpx_downstream_queue.h \
+       shrpx_log.cc shrpx_log.h \
+       shrpx_http.cc shrpx_http.h \
+       shrpx_io_control.cc shrpx_io_control.h \
+       shrpx_ssl.cc shrpx_ssl.h \
+       shrpx_worker.cc shrpx_worker.h \
+       shrpx_worker_config.cc shrpx_worker_config.h \
+       shrpx_connect_blocker.cc shrpx_connect_blocker.h \
+       shrpx_downstream_connection_pool.cc shrpx_downstream_connection_pool.h \
+       shrpx_rate_limit.cc shrpx_rate_limit.h \
+       shrpx_connection.cc shrpx_connection.h \
+       buffer.h memchunk.h template.h
+
+if HAVE_SPDYLAY
+NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
+endif # HAVE_SPDYLAY
+
+noinst_LIBRARIES = libnghttpx.a
+libnghttpx_a_SOURCES = ${NGHTTPX_SRCS}
+
+nghttpx_SOURCES = shrpx.cc shrpx.h
+nghttpx_LDADD = libnghttpx.a ${LDADD}
+
+if HAVE_CUNIT
+check_PROGRAMS += nghttpx-unittest
+nghttpx_unittest_SOURCES = shrpx-unittest.cc \
+       shrpx_ssl_test.cc shrpx_ssl_test.h \
+       shrpx_downstream_test.cc shrpx_downstream_test.h \
+       shrpx_config_test.cc shrpx_config_test.h \
+       http2_test.cc http2_test.h \
+       util_test.cc util_test.h \
+       nghttp2_gzip_test.c nghttp2_gzip_test.h \
+       nghttp2_gzip.c nghttp2_gzip.h \
+       buffer_test.cc buffer_test.h \
+       memchunk_test.cc memchunk_test.h
+nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
+       -DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
+nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
+
+TESTS += nghttpx-unittest
+endif # HAVE_CUNIT
+
+endif # ENABLE_APP
+
+if ENABLE_HPACK_TOOLS
+
+bin_PROGRAMS += inflatehd deflatehd
+
+HPACK_TOOLS_COMMON_SRCS = comp_helper.c comp_helper.h
+
+inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS)
+
+deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS)
+
+endif # ENABLE_HPACK_TOOLS
+
+if ENABLE_ASIO_LIB
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libnghttp2_asio.pc
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+lib_LTLIBRARIES = libnghttp2_asio.la
+
+libnghttp2_asio_la_SOURCES = \
+       asio_connection.h \
+       asio_server.cc asio_server.h \
+       asio_io_service_pool.cc asio_io_service_pool.h \
+       asio_http2_handler.cc asio_http2_handler.h \
+       asio_http2_impl.cc asio_http2_impl.h \
+       util.cc util.h http2.cc http2.h \
+       ssl.cc ssl.h
+
+libnghttp2_asio_la_CPPFLAGS = ${AM_CPPFLAGS} ${BOOST_CPPFLAGS}
+libnghttp2_asio_la_LDFLAGS = -no-undefined -version-info 0:0:0
+libnghttp2_asio_la_LIBADD = \
+       $(top_builddir)/lib/libnghttp2.la \
+       ${BOOST_LDFLAGS} \
+       ${BOOST_ASIO_LIB} \
+       ${BOOST_THREAD_LIB} \
+       ${BOOST_SYSTEM_LIB} \
+       @OPENSSL_LIBS@
+
+endif # ENABLE_ASIO_LIB
diff --git a/src/app_helper.cc b/src/app_helper.cc
new file mode 100644 (file)
index 0000000..f247a80
--- /dev/null
@@ -0,0 +1,524 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+#include <iostream>
+#include <string>
+#include <set>
+#include <iomanip>
+#include <fstream>
+
+#include <zlib.h>
+
+#include "app_helper.h"
+#include "util.h"
+#include "http2.h"
+
+namespace nghttp2 {
+
+namespace {
+const char *strstatus(uint32_t error_code) {
+  switch (error_code) {
+  case NGHTTP2_NO_ERROR:
+    return "NO_ERROR";
+  case NGHTTP2_PROTOCOL_ERROR:
+    return "PROTOCOL_ERROR";
+  case NGHTTP2_INTERNAL_ERROR:
+    return "INTERNAL_ERROR";
+  case NGHTTP2_FLOW_CONTROL_ERROR:
+    return "FLOW_CONTROL_ERROR";
+  case NGHTTP2_SETTINGS_TIMEOUT:
+    return "SETTINGS_TIMEOUT";
+  case NGHTTP2_STREAM_CLOSED:
+    return "STREAM_CLOSED";
+  case NGHTTP2_FRAME_SIZE_ERROR:
+    return "FRAME_SIZE_ERROR";
+  case NGHTTP2_REFUSED_STREAM:
+    return "REFUSED_STREAM";
+  case NGHTTP2_CANCEL:
+    return "CANCEL";
+  case NGHTTP2_COMPRESSION_ERROR:
+    return "COMPRESSION_ERROR";
+  case NGHTTP2_CONNECT_ERROR:
+    return "CONNECT_ERROR";
+  case NGHTTP2_ENHANCE_YOUR_CALM:
+    return "ENHANCE_YOUR_CALM";
+  case NGHTTP2_INADEQUATE_SECURITY:
+    return "INADEQUATE_SECURITY";
+  case NGHTTP2_HTTP_1_1_REQUIRED:
+    return "HTTP_1_1_REQUIRED";
+  default:
+    return "UNKNOWN";
+  }
+}
+} // namespace
+
+namespace {
+const char *strsettingsid(int32_t id) {
+  switch (id) {
+  case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
+    return "SETTINGS_HEADER_TABLE_SIZE";
+  case NGHTTP2_SETTINGS_ENABLE_PUSH:
+    return "SETTINGS_ENABLE_PUSH";
+  case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
+    return "SETTINGS_MAX_CONCURRENT_STREAMS";
+  case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
+    return "SETTINGS_INITIAL_WINDOW_SIZE";
+  case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
+    return "SETTINGS_MAX_FRAME_SIZE";
+  case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
+    return "SETTINGS_MAX_HEADER_LIST_SIZE";
+  default:
+    return "UNKNOWN";
+  }
+}
+} // namespace
+
+namespace {
+const char *strframetype(uint8_t type) {
+  switch (type) {
+  case NGHTTP2_DATA:
+    return "DATA";
+  case NGHTTP2_HEADERS:
+    return "HEADERS";
+  case NGHTTP2_PRIORITY:
+    return "PRIORITY";
+  case NGHTTP2_RST_STREAM:
+    return "RST_STREAM";
+  case NGHTTP2_SETTINGS:
+    return "SETTINGS";
+  case NGHTTP2_PUSH_PROMISE:
+    return "PUSH_PROMISE";
+  case NGHTTP2_PING:
+    return "PING";
+  case NGHTTP2_GOAWAY:
+    return "GOAWAY";
+  case NGHTTP2_WINDOW_UPDATE:
+    return "WINDOW_UPDATE";
+  case NGHTTP2_EXT_ALTSVC:
+    return "ALTSVC";
+  default:
+    return "UNKNOWN";
+  }
+};
+} // namespace
+
+namespace {
+bool color_output = false;
+} // namespace
+
+void set_color_output(bool f) { color_output = f; }
+
+namespace {
+FILE *outfile = stdout;
+} // namespace
+
+void set_output(FILE *file) { outfile = file; }
+
+namespace {
+void print_frame_attr_indent() { fprintf(outfile, "          "); }
+} // namespace
+
+namespace {
+const char *ansi_esc(const char *code) { return color_output ? code : ""; }
+} // namespace
+
+namespace {
+const char *ansi_escend() { return color_output ? "\033[0m" : ""; }
+} // namespace
+
+namespace {
+void print_nv(nghttp2_nv *nv) {
+  fprintf(outfile, "%s", ansi_esc("\033[1;34m"));
+  fwrite(nv->name, nv->namelen, 1, outfile);
+  fprintf(outfile, "%s: ", ansi_escend());
+  fwrite(nv->value, nv->valuelen, 1, outfile);
+  fprintf(outfile, "\n");
+}
+} // namespace
+namespace {
+void print_nv(nghttp2_nv *nva, size_t nvlen) {
+  auto end = nva + nvlen;
+  for (; nva != end; ++nva) {
+    print_frame_attr_indent();
+
+    print_nv(nva);
+  }
+}
+} // namelen
+
+void print_timer() {
+  auto millis = get_timer();
+  fprintf(outfile, "%s[%3ld.%03ld]%s", ansi_esc("\033[33m"),
+          (long int)(millis.count() / 1000), (long int)(millis.count() % 1000),
+          ansi_escend());
+}
+
+namespace {
+void print_frame_hd(const nghttp2_frame_hd &hd) {
+  fprintf(outfile, "<length=%zu, flags=0x%02x, stream_id=%d>\n", hd.length,
+          hd.flags, hd.stream_id);
+}
+} // namespace
+
+namespace {
+void print_flags(const nghttp2_frame_hd &hd) {
+  std::string s;
+  switch (hd.type) {
+  case NGHTTP2_DATA:
+    if (hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      s += "END_STREAM";
+    }
+    if (hd.flags & NGHTTP2_FLAG_PADDED) {
+      if (!s.empty()) {
+        s += " | ";
+      }
+      s += "PADDED";
+    }
+    break;
+  case NGHTTP2_HEADERS:
+    if (hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      s += "END_STREAM";
+    }
+    if (hd.flags & NGHTTP2_FLAG_END_HEADERS) {
+      if (!s.empty()) {
+        s += " | ";
+      }
+      s += "END_HEADERS";
+    }
+    if (hd.flags & NGHTTP2_FLAG_PADDED) {
+      if (!s.empty()) {
+        s += " | ";
+      }
+      s += "PADDED";
+    }
+    if (hd.flags & NGHTTP2_FLAG_PRIORITY) {
+      if (!s.empty()) {
+        s += " | ";
+      }
+      s += "PRIORITY";
+    }
+
+    break;
+  case NGHTTP2_PRIORITY:
+    break;
+  case NGHTTP2_SETTINGS:
+    if (hd.flags & NGHTTP2_FLAG_ACK) {
+      s += "ACK";
+    }
+    break;
+  case NGHTTP2_PUSH_PROMISE:
+    if (hd.flags & NGHTTP2_FLAG_END_HEADERS) {
+      s += "END_HEADERS";
+    }
+    if (hd.flags & NGHTTP2_FLAG_PADDED) {
+      if (!s.empty()) {
+        s += " | ";
+      }
+      s += "PADDED";
+    }
+    break;
+  case NGHTTP2_PING:
+    if (hd.flags & NGHTTP2_FLAG_ACK) {
+      s += "ACK";
+    }
+    break;
+  }
+  fprintf(outfile, "; %s\n", s.c_str());
+}
+} // namespace
+
+enum print_type { PRINT_SEND, PRINT_RECV };
+
+namespace {
+const char *frame_name_ansi_esc(print_type ptype) {
+  return ansi_esc(ptype == PRINT_SEND ? "\033[1;35m" : "\033[1;36m");
+}
+} // namespace
+
+namespace {
+void print_frame(print_type ptype, const nghttp2_frame *frame) {
+  fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype),
+          strframetype(frame->hd.type), ansi_escend());
+  print_frame_hd(frame->hd);
+  if (frame->hd.flags) {
+    print_frame_attr_indent();
+    print_flags(frame->hd);
+  }
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA:
+    if (frame->data.padlen > 0) {
+      print_frame_attr_indent();
+      fprintf(outfile, "(padlen=%zu)\n", frame->data.padlen);
+    }
+    break;
+  case NGHTTP2_HEADERS:
+    print_frame_attr_indent();
+    fprintf(outfile, "(padlen=%zu", frame->headers.padlen);
+    if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
+      fprintf(outfile, ", dep_stream_id=%d, weight=%u, exclusive=%d",
+              frame->headers.pri_spec.stream_id, frame->headers.pri_spec.weight,
+              frame->headers.pri_spec.exclusive);
+    }
+    fprintf(outfile, ")\n");
+    switch (frame->headers.cat) {
+    case NGHTTP2_HCAT_REQUEST:
+      print_frame_attr_indent();
+      fprintf(outfile, "; Open new stream\n");
+      break;
+    case NGHTTP2_HCAT_RESPONSE:
+      print_frame_attr_indent();
+      fprintf(outfile, "; First response header\n");
+      break;
+    case NGHTTP2_HCAT_PUSH_RESPONSE:
+      print_frame_attr_indent();
+      fprintf(outfile, "; First push response header\n");
+      break;
+    default:
+      break;
+    }
+    print_nv(frame->headers.nva, frame->headers.nvlen);
+    break;
+  case NGHTTP2_PRIORITY:
+    print_frame_attr_indent();
+
+    fprintf(outfile, "(dep_stream_id=%d, weight=%u, exclusive=%d)\n",
+            frame->priority.pri_spec.stream_id, frame->priority.pri_spec.weight,
+            frame->priority.pri_spec.exclusive);
+
+    break;
+  case NGHTTP2_RST_STREAM:
+    print_frame_attr_indent();
+    fprintf(outfile, "(error_code=%s(0x%02x))\n",
+            strstatus(frame->rst_stream.error_code),
+            frame->rst_stream.error_code);
+    break;
+  case NGHTTP2_SETTINGS:
+    print_frame_attr_indent();
+    fprintf(outfile, "(niv=%lu)\n",
+            static_cast<unsigned long>(frame->settings.niv));
+    for (size_t i = 0; i < frame->settings.niv; ++i) {
+      print_frame_attr_indent();
+      fprintf(outfile, "[%s(0x%02x):%u]\n",
+              strsettingsid(frame->settings.iv[i].settings_id),
+              frame->settings.iv[i].settings_id, frame->settings.iv[i].value);
+    }
+    break;
+  case NGHTTP2_PUSH_PROMISE:
+    print_frame_attr_indent();
+    fprintf(outfile, "(padlen=%zu, promised_stream_id=%d)\n",
+            frame->push_promise.padlen, frame->push_promise.promised_stream_id);
+    print_nv(frame->push_promise.nva, frame->push_promise.nvlen);
+    break;
+  case NGHTTP2_PING:
+    print_frame_attr_indent();
+    fprintf(outfile, "(opaque_data=%s)\n",
+            util::format_hex(frame->ping.opaque_data, 8).c_str());
+    break;
+  case NGHTTP2_GOAWAY:
+    print_frame_attr_indent();
+    fprintf(outfile, "(last_stream_id=%d, error_code=%s(0x%02x), "
+                     "opaque_data(%u)=[%s])\n",
+            frame->goaway.last_stream_id, strstatus(frame->goaway.error_code),
+            frame->goaway.error_code,
+            static_cast<unsigned int>(frame->goaway.opaque_data_len),
+            util::ascii_dump(frame->goaway.opaque_data,
+                             frame->goaway.opaque_data_len).c_str());
+    break;
+  case NGHTTP2_WINDOW_UPDATE:
+    print_frame_attr_indent();
+    fprintf(outfile, "(window_size_increment=%d)\n",
+            frame->window_update.window_size_increment);
+    break;
+  case NGHTTP2_EXT_ALTSVC: {
+    print_frame_attr_indent();
+
+    auto altsvc = static_cast<const nghttp2_ext_altsvc *>(frame->ext.payload);
+
+    fprintf(outfile, "(max-age=%u, port=%u, protocol_id=", altsvc->max_age,
+            altsvc->port);
+
+    if (altsvc->protocol_id_len) {
+      fwrite(altsvc->protocol_id, altsvc->protocol_id_len, 1, outfile);
+    }
+
+    fprintf(outfile, ", host=");
+
+    if (altsvc->host_len) {
+      fwrite(altsvc->host, altsvc->host_len, 1, outfile);
+    }
+
+    fprintf(outfile, ", origin=");
+
+    if (altsvc->origin_len) {
+      fwrite(altsvc->origin, altsvc->origin_len, 1, outfile);
+    }
+
+    fprintf(outfile, ")\n");
+
+    break;
+  }
+  default:
+    break;
+  }
+}
+} // namespace
+
+int verbose_on_header_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, const uint8_t *name,
+                               size_t namelen, const uint8_t *value,
+                               size_t valuelen, uint8_t flags,
+                               void *user_data) {
+  nghttp2_nv nv = {const_cast<uint8_t *>(name), const_cast<uint8_t *>(value),
+                   namelen, valuelen};
+
+  print_timer();
+  fprintf(outfile, " recv (stream_id=%d, noind=%d) ", frame->hd.stream_id,
+          (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0);
+
+  print_nv(&nv);
+  fflush(outfile);
+
+  return 0;
+}
+
+int verbose_on_frame_recv_callback(nghttp2_session *session,
+                                   const nghttp2_frame *frame,
+                                   void *user_data) {
+  print_timer();
+  fprintf(outfile, " recv ");
+  print_frame(PRINT_RECV, frame);
+  fflush(outfile);
+  return 0;
+}
+
+int verbose_on_invalid_frame_recv_callback(nghttp2_session *session,
+                                           const nghttp2_frame *frame,
+                                           uint32_t error_code,
+                                           void *user_data) {
+  print_timer();
+  fprintf(outfile, " [INVALID; status=%s] recv ", strstatus(error_code));
+  print_frame(PRINT_RECV, frame);
+  fflush(outfile);
+  return 0;
+}
+
+int verbose_on_frame_send_callback(nghttp2_session *session,
+                                   const nghttp2_frame *frame,
+                                   void *user_data) {
+  print_timer();
+  fprintf(outfile, " send ");
+  print_frame(PRINT_SEND, frame);
+  fflush(outfile);
+  return 0;
+}
+
+int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                        int32_t stream_id, const uint8_t *data,
+                                        size_t len, void *user_data) {
+  print_timer();
+  auto srecv =
+      nghttp2_session_get_stream_effective_recv_data_length(session, stream_id);
+  auto crecv = nghttp2_session_get_effective_recv_data_length(session);
+
+  fprintf(outfile,
+          " recv (stream_id=%d, length=%zu, srecv=%d, crecv=%d) DATA\n",
+          stream_id, len, srecv, crecv);
+  fflush(outfile);
+
+  return 0;
+}
+
+namespace {
+std::chrono::steady_clock::time_point base_tv;
+} // namespace
+
+void reset_timer() { base_tv = std::chrono::steady_clock::now(); }
+
+std::chrono::milliseconds get_timer() {
+  return time_delta(std::chrono::steady_clock::now(), base_tv);
+}
+
+std::chrono::steady_clock::time_point get_time() {
+  return std::chrono::steady_clock::now();
+}
+
+ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+                     size_t inlen) {
+  int rv;
+  z_stream zst;
+  uint8_t temp_out[8192];
+  auto temp_outlen = sizeof(temp_out);
+
+  zst.next_in = Z_NULL;
+  zst.zalloc = Z_NULL;
+  zst.zfree = Z_NULL;
+  zst.opaque = Z_NULL;
+
+  rv = deflateInit2(&zst, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 9,
+                    Z_DEFAULT_STRATEGY);
+
+  if (rv != Z_OK) {
+    return -1;
+  }
+
+  zst.avail_in = inlen;
+  zst.next_in = (uint8_t *)in;
+  zst.avail_out = temp_outlen;
+  zst.next_out = temp_out;
+
+  rv = deflate(&zst, Z_FINISH);
+
+  deflateEnd(&zst);
+
+  if (rv != Z_STREAM_END) {
+    return -1;
+  }
+
+  temp_outlen -= zst.avail_out;
+
+  if (temp_outlen > outlen) {
+    return -1;
+  }
+
+  memcpy(out, temp_out, temp_outlen);
+
+  return temp_outlen;
+}
+
+} // namespace nghttp2
diff --git a/src/app_helper.h b/src/app_helper.h
new file mode 100644 (file)
index 0000000..6675fde
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef APP_HELPER_H
+#define APP_HELPER_H
+
+#include "nghttp2_config.h"
+
+#include <stdint.h>
+#include <cstdlib>
+#include <sys/time.h>
+#include <poll.h>
+
+#include <map>
+#include <chrono>
+
+#include <nghttp2/nghttp2.h>
+
+namespace nghttp2 {
+
+int verbose_on_header_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, const uint8_t *name,
+                               size_t namelen, const uint8_t *value,
+                               size_t valuelen, uint8_t flags, void *user_data);
+
+int verbose_on_frame_recv_callback(nghttp2_session *session,
+                                   const nghttp2_frame *frame, void *user_data);
+
+int verbose_on_invalid_frame_recv_callback(nghttp2_session *session,
+                                           const nghttp2_frame *frame,
+                                           uint32_t error_code,
+                                           void *user_data);
+
+int verbose_on_frame_send_callback(nghttp2_session *session,
+                                   const nghttp2_frame *frame, void *user_data);
+
+int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                        int32_t stream_id, const uint8_t *data,
+                                        size_t len, void *user_data);
+
+// Returns difference between |a| and |b| in milliseconds, assuming
+// |a| is more recent than |b|.
+template <typename TimePoint>
+std::chrono::milliseconds time_delta(const TimePoint &a, const TimePoint &b) {
+  return std::chrono::duration_cast<std::chrono::milliseconds>(a - b);
+}
+
+// Resets timer
+void reset_timer();
+
+// Returns the duration since timer reset.
+std::chrono::milliseconds get_timer();
+
+// Returns current time point.
+std::chrono::steady_clock::time_point get_time();
+
+void print_timer();
+
+// Setting true will print characters with ANSI color escape codes
+// when printing HTTP2 frames. This function changes a static
+// variable.
+void set_color_output(bool f);
+
+// Set output file when printing HTTP2 frames. By default, stdout is
+// used.
+void set_output(FILE *file);
+
+ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+                     size_t inlen);
+
+} // namespace nghttp2
+
+#endif // APP_HELPER_H
diff --git a/src/asio_connection.h b/src/asio_connection.h
new file mode 100644 (file)
index 0000000..02d8ec9
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// connection.hpp
+// ~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_SERVER2_CONNECTION_HPP
+#define HTTP_SERVER2_CONNECTION_HPP
+
+#include "nghttp2_config.h"
+
+#include <memory>
+
+#include <boost/asio.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/array.hpp>
+
+#include <nghttp2/asio_http2.h>
+#include "asio_http2_handler.h"
+#include "util.h"
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+namespace server {
+
+/// Represents a single connection from a client.
+template <typename socket_type>
+class connection : public std::enable_shared_from_this<connection<socket_type>>,
+                   private boost::noncopyable {
+public:
+  /// Construct a connection with the given io_service.
+  template <typename... SocketArgs>
+  explicit connection(request_cb cb, boost::asio::io_service &task_io_service,
+                      SocketArgs &&... args)
+      : socket_(std::forward<SocketArgs>(args)...), request_cb_(std::move(cb)),
+        task_io_service_(task_io_service), writing_(false) {}
+
+  /// Start the first asynchronous operation for the connection.
+  void start() {
+    handler_ = std::make_shared<http2_handler>(
+        socket_.get_io_service(), task_io_service_, [this]() { do_write(); },
+        request_cb_);
+    if (handler_->start() != 0) {
+      return;
+    }
+    do_read();
+  }
+
+  socket_type &socket() { return socket_; }
+
+  void do_read() {
+    auto self = this->shared_from_this();
+
+    socket_.async_read_some(boost::asio::buffer(buffer_),
+                            [this, self](const boost::system::error_code &e,
+                                         std::size_t bytes_transferred) {
+      if (!e) {
+        if (handler_->on_read(buffer_, bytes_transferred) != 0) {
+          return;
+        }
+
+        do_write();
+
+        if (!writing_ && handler_->should_stop()) {
+          return;
+        }
+
+        do_read();
+      }
+
+      // If an error occurs then no new asynchronous operations are
+      // started. This means that all shared_ptr references to the
+      // connection object will disappear and the object will be
+      // destroyed automatically after this handler returns. The
+      // connection class's destructor closes the socket.
+    });
+  }
+
+  void do_write() {
+    auto self = this->shared_from_this();
+
+    if (writing_) {
+      return;
+    }
+
+    int rv;
+    std::size_t nwrite;
+
+    rv = handler_->on_write(outbuf_, nwrite);
+
+    if (rv != 0) {
+      return;
+    }
+
+    if (nwrite == 0) {
+      return;
+    }
+
+    writing_ = true;
+
+    boost::asio::async_write(
+        socket_, boost::asio::buffer(outbuf_, nwrite),
+        [this, self](const boost::system::error_code &e, std::size_t) {
+          if (!e) {
+            writing_ = false;
+
+            do_write();
+          }
+        });
+
+    // No new asynchronous operations are started. This means that all
+    // shared_ptr references to the connection object will disappear and
+    // the object will be destroyed automatically after this handler
+    // returns. The connection class's destructor closes the socket.
+  }
+
+private:
+  socket_type socket_;
+
+  request_cb request_cb_;
+
+  boost::asio::io_service &task_io_service_;
+
+  std::shared_ptr<http2_handler> handler_;
+
+  /// Buffer for incoming data.
+  boost::array<uint8_t, 8192> buffer_;
+
+  boost::array<uint8_t, 16394> outbuf_;
+
+  bool writing_;
+};
+
+} // namespace server
+
+} // namespace asio_http2
+
+} // namespace nghttp2
+
+#endif // HTTP_SERVER2_CONNECTION_HPP
diff --git a/src/asio_http2_handler.cc b/src/asio_http2_handler.cc
new file mode 100644 (file)
index 0000000..c08345b
--- /dev/null
@@ -0,0 +1,746 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "asio_http2_handler.h"
+
+#include <iostream>
+
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+channel::channel() : impl_(make_unique<channel_impl>()) {}
+
+void channel::post(void_cb cb) { impl_->post(std::move(cb)); }
+
+channel_impl &channel::impl() { return *impl_; }
+
+channel_impl::channel_impl() : strand_(nullptr) {}
+
+void channel_impl::post(void_cb cb) { strand_->post(std::move(cb)); }
+
+void channel_impl::strand(boost::asio::io_service::strand *strand) {
+  strand_ = strand;
+}
+
+namespace server {
+
+extern std::shared_ptr<std::string> cached_date;
+
+request::request() : impl_(make_unique<request_impl>()) {}
+
+const std::vector<header> &request::headers() const { return impl_->headers(); }
+
+const std::string &request::method() const { return impl_->method(); }
+
+const std::string &request::scheme() const { return impl_->scheme(); }
+
+const std::string &request::authority() const { return impl_->authority(); }
+
+const std::string &request::host() const { return impl_->host(); }
+
+const std::string &request::path() const { return impl_->path(); }
+
+bool request::push(std::string method, std::string path,
+                   std::vector<header> headers) {
+  return impl_->push(std::move(method), std::move(path), std::move(headers));
+}
+
+bool request::pushed() const { return impl_->pushed(); }
+
+bool request::closed() const { return impl_->closed(); }
+
+void request::on_data(data_cb cb) { return impl_->on_data(std::move(cb)); }
+
+void request::on_end(void_cb cb) { return impl_->on_end(std::move(cb)); }
+
+bool request::run_task(thread_cb start) {
+  return impl_->run_task(std::move(start));
+}
+
+request_impl &request::impl() { return *impl_; }
+
+response::response() : impl_(make_unique<response_impl>()) {}
+
+void response::write_head(unsigned int status_code,
+                          std::vector<header> headers) {
+  impl_->write_head(status_code, std::move(headers));
+}
+
+void response::end(std::string data) { impl_->end(std::move(data)); }
+
+void response::end(read_cb cb) { impl_->end(std::move(cb)); }
+
+void response::resume() { impl_->resume(); }
+
+unsigned int response::status_code() const { return impl_->status_code(); }
+
+bool response::started() const { return impl_->started(); }
+
+response_impl &response::impl() { return *impl_; }
+
+request_impl::request_impl() : pushed_(false) {}
+
+const std::vector<header> &request_impl::headers() const { return headers_; }
+
+const std::string &request_impl::method() const { return method_; }
+
+const std::string &request_impl::scheme() const { return scheme_; }
+
+const std::string &request_impl::authority() const { return authority_; }
+
+const std::string &request_impl::host() const { return host_; }
+
+const std::string &request_impl::path() const { return path_; }
+
+void request_impl::set_header(std::vector<header> headers) {
+  headers_ = std::move(headers);
+}
+
+void request_impl::add_header(std::string name, std::string value) {
+  headers_.push_back(header{std::move(name), std::move(value)});
+}
+
+void request_impl::method(std::string arg) { method_ = std::move(arg); }
+
+void request_impl::scheme(std::string arg) { scheme_ = std::move(arg); }
+
+void request_impl::authority(std::string arg) { authority_ = std::move(arg); }
+
+void request_impl::host(std::string arg) { host_ = std::move(arg); }
+
+void request_impl::path(std::string arg) { path_ = std::move(arg); }
+
+bool request_impl::push(std::string method, std::string path,
+                        std::vector<header> headers) {
+  if (closed()) {
+    return false;
+  }
+
+  auto handler = handler_.lock();
+  auto stream = stream_.lock();
+  auto rv = handler->push_promise(*stream, std::move(method), std::move(path),
+                                  std::move(headers));
+  return rv == 0;
+}
+
+bool request_impl::pushed() const { return pushed_; }
+
+void request_impl::pushed(bool f) { pushed_ = f; }
+
+bool request_impl::closed() const {
+  return handler_.expired() || stream_.expired();
+}
+
+void request_impl::on_data(data_cb cb) { on_data_cb_ = std::move(cb); }
+
+void request_impl::on_end(void_cb cb) { on_end_cb_ = std::move(cb); }
+
+bool request_impl::run_task(thread_cb start) {
+  if (closed()) {
+    return false;
+  }
+
+  auto handler = handler_.lock();
+
+  return handler->run_task(std::move(start));
+}
+
+void request_impl::handler(std::weak_ptr<http2_handler> h) {
+  handler_ = std::move(h);
+}
+
+void request_impl::stream(std::weak_ptr<http2_stream> s) {
+  stream_ = std::move(s);
+}
+
+void request_impl::call_on_data(const uint8_t *data, std::size_t len) {
+  if (on_data_cb_) {
+    on_data_cb_(data, len);
+  }
+}
+
+void request_impl::call_on_end() {
+  if (on_end_cb_) {
+    on_end_cb_();
+  }
+}
+
+response_impl::response_impl() : status_code_(200), started_(false) {}
+
+unsigned int response_impl::status_code() const { return status_code_; }
+
+void response_impl::write_head(unsigned int status_code,
+                               std::vector<header> headers) {
+  status_code_ = status_code;
+  headers_ = std::move(headers);
+}
+
+void response_impl::end(std::string data) {
+  if (started_) {
+    return;
+  }
+
+  auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data),
+                                                                data.size());
+  auto read_cb = [strio](uint8_t *buf, size_t len) {
+    auto nread = std::min(len, strio->second);
+    memcpy(buf, strio->first.c_str(), nread);
+    strio->second -= nread;
+    if (strio->second == 0) {
+      return std::make_pair(nread, true);
+    }
+
+    return std::make_pair(nread, false);
+  };
+
+  end(std::move(read_cb));
+}
+
+void response_impl::end(read_cb cb) {
+  if (started_ || closed()) {
+    return;
+  }
+
+  read_cb_ = std::move(cb);
+  started_ = true;
+
+  auto handler = handler_.lock();
+  auto stream = stream_.lock();
+
+  if (handler->start_response(*stream) != 0) {
+    handler->stream_error(stream->get_stream_id(), NGHTTP2_INTERNAL_ERROR);
+    return;
+  }
+
+  if (!handler->inside_callback()) {
+    handler->initiate_write();
+  }
+}
+
+bool response_impl::closed() const {
+  return handler_.expired() || stream_.expired();
+}
+
+void response_impl::resume() {
+  if (closed()) {
+    return;
+  }
+
+  auto handler = handler_.lock();
+  auto stream = stream_.lock();
+  handler->resume(*stream);
+
+  if (!handler->inside_callback()) {
+    handler->initiate_write();
+  }
+}
+
+bool response_impl::started() const { return started_; }
+
+const std::vector<header> &response_impl::headers() const { return headers_; }
+
+void response_impl::handler(std::weak_ptr<http2_handler> h) {
+  handler_ = std::move(h);
+}
+
+void response_impl::stream(std::weak_ptr<http2_stream> s) {
+  stream_ = std::move(s);
+}
+
+std::pair<ssize_t, bool> response_impl::call_read(uint8_t *data,
+                                                  std::size_t len) {
+  if (read_cb_) {
+    return read_cb_(data, len);
+  }
+
+  return std::make_pair(0, true);
+}
+
+http2_stream::http2_stream(int32_t stream_id)
+    : request_(std::make_shared<request>()),
+      response_(std::make_shared<response>()), stream_id_(stream_id) {}
+
+int32_t http2_stream::get_stream_id() const { return stream_id_; }
+
+const std::shared_ptr<request> &http2_stream::get_request() { return request_; }
+
+const std::shared_ptr<response> &http2_stream::get_response() {
+  return response_;
+}
+
+namespace {
+int stream_error(nghttp2_session *session, int32_t stream_id,
+                 uint32_t error_code) {
+  return nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
+                                   error_code);
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto handler = static_cast<http2_handler *>(user_data);
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  handler->create_stream(frame->hd.stream_id);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                       const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen, uint8_t flags,
+                       void *user_data) {
+  auto handler = static_cast<http2_handler *>(user_data);
+  auto stream_id = frame->hd.stream_id;
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  auto stream = handler->find_stream(stream_id);
+  if (!stream) {
+    return 0;
+  }
+
+  if (!nghttp2_check_header_name(name, namelen) ||
+      !nghttp2_check_header_value(value, valuelen)) {
+    stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  auto &req = stream->get_request()->impl();
+
+  if (name[0] == ':' && !req.headers().empty()) {
+    stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  if (util::streq(":method", name, namelen)) {
+    if (!req.method().empty()) {
+      stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    req.method(std::string(value, value + valuelen));
+  } else if (util::streq(":scheme", name, namelen)) {
+    if (!req.scheme().empty()) {
+      stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    req.scheme(std::string(value, value + valuelen));
+  } else if (util::streq(":authority", name, namelen)) {
+    if (!req.authority().empty()) {
+      stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    req.authority(std::string(value, value + valuelen));
+  } else if (util::streq(":path", name, namelen)) {
+    if (!req.path().empty()) {
+      stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    req.path(std::string(value, value + valuelen));
+  } else {
+    if (name[0] == ':') {
+      stream_error(session, stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+
+    if (util::streq("host", name, namelen)) {
+      req.host(std::string(value, value + valuelen));
+    }
+
+    req.add_header(std::string(name, name + namelen),
+                   std::string(value, value + valuelen));
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  auto handler = static_cast<http2_handler *>(user_data);
+  auto stream = handler->find_stream(frame->hd.stream_id);
+
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA:
+    if (!stream) {
+      break;
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      stream->get_request()->impl().call_on_end();
+    }
+
+    break;
+  case NGHTTP2_HEADERS: {
+    if (!stream || frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+      break;
+    }
+
+    auto &req = stream->get_request()->impl();
+
+    if (req.method().empty() || req.scheme().empty() || req.path().empty() ||
+        (req.authority().empty() && req.host().empty())) {
+      stream_error(session, frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
+      return 0;
+    }
+
+    if (req.host().empty()) {
+      req.host(req.authority());
+    }
+
+    handler->call_on_request(*stream);
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      stream->get_request()->impl().call_on_end();
+    }
+
+    break;
+  }
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) {
+  auto handler = static_cast<http2_handler *>(user_data);
+  auto stream = handler->find_stream(stream_id);
+
+  if (!stream) {
+    return 0;
+  }
+
+  stream->get_request()->impl().call_on_data(data, len);
+
+  return 0;
+}
+
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                             uint32_t error_code, void *user_data) {
+  auto handler = static_cast<http2_handler *>(user_data);
+
+  handler->close_stream(stream_id);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  auto handler = static_cast<http2_handler *>(user_data);
+
+  if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
+    return 0;
+  }
+
+  auto stream = handler->find_stream(frame->push_promise.promised_stream_id);
+
+  if (!stream) {
+    return 0;
+  }
+
+  handler->call_on_request(*stream);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_not_send_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, int lib_error_code,
+                               void *user_data) {
+  if (frame->hd.type != NGHTTP2_HEADERS) {
+    return 0;
+  }
+
+  // Issue RST_STREAM so that stream does not hang around.
+  nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
+                            NGHTTP2_INTERNAL_ERROR);
+
+  return 0;
+}
+} // namespace
+
+http2_handler::http2_handler(boost::asio::io_service &io_service,
+                             boost::asio::io_service &task_io_service_,
+                             connection_write writefun, request_cb cb)
+    : writefun_(writefun), request_cb_(std::move(cb)), io_service_(io_service),
+      task_io_service_(task_io_service_),
+      strand_(std::make_shared<boost::asio::io_service::strand>(io_service_)),
+      session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {}
+
+http2_handler::~http2_handler() { nghttp2_session_del(session_); }
+
+int http2_handler::start() {
+  int rv;
+
+  nghttp2_session_callbacks *callbacks;
+  rv = nghttp2_session_callbacks_new(&callbacks);
+  if (rv != 0) {
+    return -1;
+  }
+
+  auto cb_del = defer(nghttp2_session_callbacks_del, callbacks);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+  nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+                                                       on_frame_send_callback);
+  nghttp2_session_callbacks_set_on_frame_not_send_callback(
+      callbacks, on_frame_not_send_callback);
+
+  nghttp2_option *option;
+  rv = nghttp2_option_new(&option);
+  if (rv != 0) {
+    return -1;
+  }
+
+  auto opt_del = defer(nghttp2_option_del, option);
+
+  nghttp2_option_set_recv_client_preface(option, 1);
+
+  rv = nghttp2_session_server_new2(&session_, callbacks, this, option);
+  if (rv != 0) {
+    return -1;
+  }
+
+  nghttp2_settings_entry ent{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100};
+  nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &ent, 1);
+
+  return 0;
+}
+
+std::shared_ptr<http2_stream> http2_handler::create_stream(int32_t stream_id) {
+  auto stream = std::make_shared<http2_stream>(stream_id);
+  streams_.emplace(stream_id, stream);
+
+  auto self = shared_from_this();
+  auto &req = stream->get_request()->impl();
+  auto &res = stream->get_response()->impl();
+  req.handler(self);
+  req.stream(stream);
+  res.handler(self);
+  res.stream(stream);
+
+  return stream;
+}
+
+void http2_handler::close_stream(int32_t stream_id) {
+  streams_.erase(stream_id);
+}
+
+std::shared_ptr<http2_stream> http2_handler::find_stream(int32_t stream_id) {
+  auto i = streams_.find(stream_id);
+  if (i == std::end(streams_)) {
+    return nullptr;
+  }
+
+  return (*i).second;
+}
+
+void http2_handler::call_on_request(http2_stream &stream) {
+  request_cb_(stream.get_request(), stream.get_response());
+}
+
+bool http2_handler::should_stop() const {
+  return !nghttp2_session_want_read(session_) &&
+         !nghttp2_session_want_write(session_);
+}
+
+int http2_handler::start_response(http2_stream &stream) {
+  int rv;
+
+  auto &res = stream.get_response()->impl();
+  auto &headers = res.headers();
+  auto nva = std::vector<nghttp2_nv>();
+  nva.reserve(2 + headers.size());
+  auto status = util::utos(res.status_code());
+  auto date = cached_date;
+  nva.push_back(nghttp2::http2::make_nv_ls(":status", status));
+  nva.push_back(nghttp2::http2::make_nv_ls("date", *date));
+  for (auto &hd : headers) {
+    nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value));
+  }
+
+  nghttp2_data_provider prd;
+  prd.source.ptr = &stream;
+  prd.read_callback =
+      [](nghttp2_session *session, int32_t stream_id, uint8_t *buf,
+         size_t length, uint32_t *data_flags, nghttp2_data_source *source,
+         void *user_data) -> ssize_t {
+    auto &stream = *static_cast<http2_stream *>(source->ptr);
+    auto rv = stream.get_response()->impl().call_read(buf, length);
+    if (rv.first < 0) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+
+    if (rv.second) {
+      *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+    } else if (rv.first == 0) {
+      return NGHTTP2_ERR_DEFERRED;
+    }
+
+    return rv.first;
+  };
+
+  rv = nghttp2_submit_response(session_, stream.get_stream_id(), nva.data(),
+                               nva.size(), &prd);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+void http2_handler::enter_callback() {
+  assert(!inside_callback_);
+  inside_callback_ = true;
+}
+
+void http2_handler::leave_callback() {
+  assert(inside_callback_);
+  inside_callback_ = false;
+}
+
+bool http2_handler::inside_callback() const { return inside_callback_; }
+
+void http2_handler::stream_error(int32_t stream_id, uint32_t error_code) {
+  ::nghttp2::asio_http2::server::stream_error(session_, stream_id, error_code);
+}
+
+void http2_handler::initiate_write() { writefun_(); }
+
+void http2_handler::resume(http2_stream &stream) {
+  nghttp2_session_resume_data(session_, stream.get_stream_id());
+}
+
+int http2_handler::push_promise(http2_stream &stream, std::string method,
+                                std::string path, std::vector<header> headers) {
+  int rv;
+
+  auto &req = stream.get_request()->impl();
+
+  auto nva = std::vector<nghttp2_nv>();
+  nva.reserve(5 + headers.size());
+  nva.push_back(nghttp2::http2::make_nv_ls(":method", method));
+  nva.push_back(nghttp2::http2::make_nv_ls(":scheme", req.scheme()));
+  if (!req.authority().empty()) {
+    nva.push_back(nghttp2::http2::make_nv_ls(":authority", req.authority()));
+  }
+  nva.push_back(nghttp2::http2::make_nv_ls(":path", path));
+  if (!req.host().empty()) {
+    nva.push_back(nghttp2::http2::make_nv_ls("host", req.host()));
+  }
+
+  for (auto &hd : headers) {
+    nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value));
+  }
+
+  rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
+                                   stream.get_stream_id(), nva.data(),
+                                   nva.size(), nullptr);
+
+  if (rv < 0) {
+    return -1;
+  }
+
+  auto promised_stream = create_stream(rv);
+  auto &promised_req = promised_stream->get_request()->impl();
+  promised_req.pushed(true);
+  promised_req.method(std::move(method));
+  promised_req.scheme(req.scheme());
+  promised_req.authority(req.authority());
+  promised_req.path(std::move(path));
+  promised_req.host(req.host());
+  promised_req.set_header(std::move(headers));
+  if (!req.host().empty()) {
+    promised_req.add_header("host", req.host());
+  }
+
+  return 0;
+}
+
+bool http2_handler::run_task(thread_cb start) {
+  auto strand = strand_;
+
+  try {
+    task_io_service_.post([start, strand]() {
+      channel chan;
+      chan.impl().strand(strand.get());
+
+      start(chan);
+    });
+
+    return true;
+  } catch (std::exception &ex) {
+    return false;
+  }
+}
+
+boost::asio::io_service &http2_handler::io_service() { return io_service_; }
+
+callback_guard::callback_guard(http2_handler &h) : handler(h) {
+  handler.enter_callback();
+}
+
+callback_guard::~callback_guard() { handler.leave_callback(); }
+
+} // namespace server
+
+} // namespace asio_http2
+
+} // namespace nghttp2
diff --git a/src/asio_http2_handler.h b/src/asio_http2_handler.h
new file mode 100644 (file)
index 0000000..5dc7121
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTTP2_HANDLER_H
+#define HTTP2_HANDLER_H
+
+#include "nghttp2_config.h"
+
+#include <map>
+#include <vector>
+#include <functional>
+#include <string>
+#include <boost/array.hpp>
+#include <boost/asio.hpp>
+
+#include <nghttp2/nghttp2.h>
+
+#include <nghttp2/asio_http2.h>
+
+namespace nghttp2 {
+namespace asio_http2 {
+
+class channel_impl {
+public:
+  channel_impl();
+  void post(void_cb cb);
+  void strand(boost::asio::io_service::strand *strand);
+
+private:
+  boost::asio::io_service::strand *strand_;
+};
+
+namespace server {
+
+class http2_handler;
+class http2_stream;
+
+class request_impl {
+public:
+  request_impl();
+
+  const std::vector<header> &headers() const;
+  const std::string &method() const;
+  const std::string &scheme() const;
+  const std::string &authority() const;
+  const std::string &host() const;
+  const std::string &path() const;
+
+  bool push(std::string method, std::string path,
+            std::vector<header> headers = {});
+
+  bool pushed() const;
+  bool closed() const;
+
+  void on_data(data_cb cb);
+  void on_end(void_cb cb);
+
+  bool run_task(thread_cb start);
+
+  void set_header(std::vector<header> headers);
+  void add_header(std::string name, std::string value);
+  void method(std::string method);
+  void scheme(std::string scheme);
+  void authority(std::string authority);
+  void host(std::string host);
+  void path(std::string path);
+  void pushed(bool f);
+  void handler(std::weak_ptr<http2_handler> h);
+  void stream(std::weak_ptr<http2_stream> s);
+  void call_on_data(const uint8_t *data, std::size_t len);
+  void call_on_end();
+
+private:
+  std::vector<header> headers_;
+  std::string method_;
+  std::string scheme_;
+  std::string authority_;
+  std::string host_;
+  std::string path_;
+  data_cb on_data_cb_;
+  void_cb on_end_cb_;
+  std::weak_ptr<http2_handler> handler_;
+  std::weak_ptr<http2_stream> stream_;
+  bool pushed_;
+};
+
+class response_impl {
+public:
+  response_impl();
+  void write_head(unsigned int status_code, std::vector<header> headers = {});
+  void end(std::string data = "");
+  void end(read_cb cb);
+  void resume();
+  bool closed() const;
+
+  unsigned int status_code() const;
+  const std::vector<header> &headers() const;
+  bool started() const;
+  void handler(std::weak_ptr<http2_handler> h);
+  void stream(std::weak_ptr<http2_stream> s);
+  read_cb::result_type call_read(uint8_t *data, std::size_t len);
+
+private:
+  std::vector<header> headers_;
+  read_cb read_cb_;
+  std::weak_ptr<http2_handler> handler_;
+  std::weak_ptr<http2_stream> stream_;
+  unsigned int status_code_;
+  bool started_;
+};
+
+class http2_stream {
+public:
+  http2_stream(int32_t stream_id);
+
+  int32_t get_stream_id() const;
+  const std::shared_ptr<request> &get_request();
+  const std::shared_ptr<response> &get_response();
+
+private:
+  std::shared_ptr<request> request_;
+  std::shared_ptr<response> response_;
+  int32_t stream_id_;
+};
+
+struct callback_guard {
+  callback_guard(http2_handler &h);
+  ~callback_guard();
+  http2_handler &handler;
+};
+
+typedef std::function<void(void)> connection_write;
+
+class http2_handler : public std::enable_shared_from_this<http2_handler> {
+public:
+  http2_handler(boost::asio::io_service &io_service,
+                boost::asio::io_service &task_io_service,
+                connection_write writefun, request_cb cb);
+
+  ~http2_handler();
+
+  int start();
+
+  std::shared_ptr<http2_stream> create_stream(int32_t stream_id);
+  void close_stream(int32_t stream_id);
+  std::shared_ptr<http2_stream> find_stream(int32_t stream_id);
+
+  void call_on_request(http2_stream &stream);
+
+  bool should_stop() const;
+
+  int start_response(http2_stream &stream);
+
+  void stream_error(int32_t stream_id, uint32_t error_code);
+
+  void initiate_write();
+
+  void enter_callback();
+  void leave_callback();
+  bool inside_callback() const;
+
+  void resume(http2_stream &stream);
+
+  int push_promise(http2_stream &stream, std::string method, std::string path,
+                   std::vector<header> headers);
+
+  bool run_task(thread_cb start);
+
+  boost::asio::io_service &io_service();
+
+  template <size_t N>
+  int on_read(const boost::array<uint8_t, N> &buffer, std::size_t len) {
+    callback_guard cg(*this);
+
+    int rv;
+
+    rv = nghttp2_session_mem_recv(session_, buffer.data(), len);
+
+    if (rv < 0) {
+      return -1;
+    }
+
+    return 0;
+  }
+
+  template <size_t N>
+  int on_write(boost::array<uint8_t, N> &buffer, std::size_t &len) {
+    callback_guard cg(*this);
+
+    len = 0;
+
+    if (buf_) {
+      std::copy_n(buf_, buflen_, std::begin(buffer));
+
+      len += buflen_;
+
+      buf_ = nullptr;
+      buflen_ = 0;
+    }
+
+    for (;;) {
+      const uint8_t *data;
+      auto nread = nghttp2_session_mem_send(session_, &data);
+      if (nread < 0) {
+        return -1;
+      }
+
+      if (nread == 0) {
+        break;
+      }
+
+      if (len + nread > buffer.size()) {
+        buf_ = data;
+        buflen_ = nread;
+
+        break;
+      }
+
+      std::copy_n(data, nread, std::begin(buffer) + len);
+
+      len += nread;
+    }
+
+    return 0;
+  }
+
+private:
+  std::map<int32_t, std::shared_ptr<http2_stream>> streams_;
+  connection_write writefun_;
+  request_cb request_cb_;
+  boost::asio::io_service &io_service_;
+  boost::asio::io_service &task_io_service_;
+  std::shared_ptr<boost::asio::io_service::strand> strand_;
+  nghttp2_session *session_;
+  const uint8_t *buf_;
+  std::size_t buflen_;
+  bool inside_callback_;
+};
+
+} // namespace server
+} // namespace asio_http2
+} // namespace nghttp
+
+#endif // HTTP2_HANDLER_H
diff --git a/src/asio_http2_impl.cc b/src/asio_http2_impl.cc
new file mode 100644 (file)
index 0000000..051e4da
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "asio_http2_impl.h"
+
+#include <boost/asio/ssl.hpp>
+
+#include <openssl/ssl.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "asio_server.h"
+#include "util.h"
+#include "ssl.h"
+#include "template.h"
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+namespace server {
+
+http2::http2() : impl_(make_unique<http2_impl>()) {}
+
+http2::~http2() {}
+
+void http2::listen(const std::string &address, uint16_t port, request_cb cb) {
+  impl_->listen(address, port, std::move(cb));
+}
+
+void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); }
+
+void http2::tls(std::string private_key_file, std::string certificate_file) {
+  impl_->tls(std::move(private_key_file), std::move(certificate_file));
+}
+
+void http2::num_concurrent_tasks(size_t num_concurrent_tasks) {
+  impl_->num_concurrent_tasks(num_concurrent_tasks);
+}
+
+void http2::backlog(int backlog) { impl_->backlog(backlog); }
+
+http2_impl::http2_impl()
+    : num_threads_(1), num_concurrent_tasks_(1), backlog_(-1) {}
+
+namespace {
+std::vector<unsigned char> &get_alpn_token() {
+  static auto alpn_token = util::get_default_alpn();
+  return alpn_token;
+}
+} // namespace
+
+void http2_impl::listen(const std::string &address, uint16_t port,
+                        request_cb cb) {
+  std::unique_ptr<boost::asio::ssl::context> ssl_ctx;
+
+  if (!private_key_file_.empty() && !certificate_file_.empty()) {
+    ssl_ctx = make_unique<boost::asio::ssl::context>(
+        boost::asio::ssl::context::sslv23);
+
+    ssl_ctx->use_private_key_file(private_key_file_,
+                                  boost::asio::ssl::context::pem);
+    ssl_ctx->use_certificate_chain_file(certificate_file_);
+
+    auto ctx = ssl_ctx->native_handle();
+
+    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                                 SSL_OP_NO_COMPRESSION |
+                                 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+                                 SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_TICKET |
+                                 SSL_OP_CIPHER_SERVER_PREFERENCE);
+    SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+    SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
+    SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
+
+    SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST);
+
+    auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+    if (ecdh) {
+      SSL_CTX_set_tmp_ecdh(ctx, ecdh);
+      EC_KEY_free(ecdh);
+    }
+
+    SSL_CTX_set_next_protos_advertised_cb(
+        ctx,
+        [](SSL *s, const unsigned char **data, unsigned int *len, void *arg) {
+          auto &token = get_alpn_token();
+
+          *data = token.data();
+          *len = token.size();
+
+          return SSL_TLSEXT_ERR_OK;
+        },
+        nullptr);
+  }
+
+  server(address, port, num_threads_, num_concurrent_tasks_, std::move(cb),
+         std::move(ssl_ctx), backlog_).run();
+}
+
+void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; }
+
+void http2_impl::tls(std::string private_key_file,
+                     std::string certificate_file) {
+  private_key_file_ = std::move(private_key_file);
+  certificate_file_ = std::move(certificate_file);
+}
+
+void http2_impl::num_concurrent_tasks(size_t num_concurrent_tasks) {
+  num_concurrent_tasks_ = num_concurrent_tasks;
+}
+
+void http2_impl::backlog(int backlog) { backlog_ = backlog; }
+
+} // namespace server
+
+template <typename F, typename... T>
+std::shared_ptr<Defer<F, T...>> defer_shared(F &&f, T &&... t) {
+  return std::make_shared<Defer<F, T...>>(std::forward<F>(f),
+                                          std::forward<T>(t)...);
+}
+
+read_cb file_reader(const std::string &path) {
+  auto fd = open(path.c_str(), O_RDONLY);
+  if (fd == -1) {
+    return read_cb();
+  }
+
+  return file_reader_from_fd(fd);
+}
+
+read_cb file_reader_from_fd(int fd) {
+  auto d = defer_shared(close, fd);
+
+  return [fd, d](uint8_t *buf, size_t len) -> read_cb::result_type {
+    int rv;
+    while ((rv = read(fd, buf, len)) == -1 && errno == EINTR)
+      ;
+
+    if (rv == -1) {
+      return std::make_pair(-1, false);
+    }
+
+    if (rv == 0) {
+      return std::make_pair(rv, true);
+    }
+
+    return std::make_pair(rv, false);
+  };
+}
+
+bool check_path(const std::string &path) { return util::check_path(path); }
+
+std::string percent_decode(const std::string &s) {
+  return util::percentDecode(std::begin(s), std::end(s));
+}
+
+std::string http_date(int64_t t) { return util::http_date(t); }
+
+} // namespace asio_http2
+
+} // namespace nghttp2
diff --git a/src/asio_http2_impl.h b/src/asio_http2_impl.h
new file mode 100644 (file)
index 0000000..3b53105
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef ASIO_HTTP2_IMPL_H
+#define ASIO_HTTP2_IMPL_H
+
+#include "nghttp2_config.h"
+
+#include <nghttp2/asio_http2.h>
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+namespace server {
+
+class server;
+
+class http2_impl {
+public:
+  http2_impl();
+  void listen(const std::string &address, uint16_t port, request_cb cb);
+  void num_threads(size_t num_threads);
+  void tls(std::string private_key_file, std::string certificate_file);
+  void num_concurrent_tasks(size_t num_concurrent_tasks);
+  void backlog(int backlog);
+
+private:
+  std::string private_key_file_;
+  std::string certificate_file_;
+  std::unique_ptr<server> server_;
+  std::size_t num_threads_;
+  std::size_t num_concurrent_tasks_;
+  int backlog_;
+};
+
+} // namespace server
+
+} // namespace asio_http2
+
+} // namespace nghttp2
+
+#endif // ASIO_HTTP2_IMPL_H
diff --git a/src/asio_io_service_pool.cc b/src/asio_io_service_pool.cc
new file mode 100644 (file)
index 0000000..4b9e39e
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// io_service_pool.cpp
+// ~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "asio_server.h"
+#include <stdexcept>
+#include <future>
+#include <boost/thread/thread.hpp>
+#include <boost/bind.hpp>
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+namespace server {
+
+io_service_pool::io_service_pool(std::size_t pool_size,
+                                 std::size_t thread_pool_size)
+    : next_io_service_(0), thread_pool_size_(thread_pool_size) {
+  if (pool_size == 0) {
+    throw std::runtime_error("io_service_pool size is 0");
+  }
+
+  // Give all the io_services work to do so that their run() functions will not
+  // exit until they are explicitly stopped.
+  for (std::size_t i = 0; i < pool_size; ++i) {
+    auto io_service = std::make_shared<boost::asio::io_service>();
+    auto work = std::make_shared<boost::asio::io_service::work>(*io_service);
+    io_services_.push_back(io_service);
+    work_.push_back(work);
+  }
+
+  auto work = std::make_shared<boost::asio::io_service::work>(task_io_service_);
+  work_.push_back(work);
+}
+
+void io_service_pool::run() {
+  for (std::size_t i = 0; i < thread_pool_size_; ++i) {
+    thread_pool_.create_thread([this]() { task_io_service_.run(); });
+  }
+
+  // Create a pool of threads to run all of the io_services.
+  auto futs = std::vector<std::future<std::size_t>>();
+
+  for (std::size_t i = 0; i < io_services_.size(); ++i) {
+    futs.push_back(std::async(std::launch::async,
+                              (size_t (boost::asio::io_service::*)(void)) &
+                                  boost::asio::io_service::run,
+                              io_services_[i]));
+  }
+
+  // Wait for all threads in the pool to exit.
+  for (auto &fut : futs) {
+    fut.get();
+  }
+
+  thread_pool_.join_all();
+}
+
+void io_service_pool::stop() {
+  // Explicitly stop all io_services.
+  for (auto &iosv : io_services_) {
+    iosv->stop();
+  }
+
+  task_io_service_.stop();
+}
+
+boost::asio::io_service &io_service_pool::get_io_service() {
+  // Use a round-robin scheme to choose the next io_service to use.
+  auto &io_service = *io_services_[next_io_service_];
+  ++next_io_service_;
+  if (next_io_service_ == io_services_.size()) {
+    next_io_service_ = 0;
+  }
+  return io_service;
+}
+
+boost::asio::io_service &io_service_pool::get_task_io_service() {
+  return task_io_service_;
+}
+
+} // namespace server
+
+} // namespace asio_http2
+
+} // namespace nghttp2
diff --git a/src/asio_io_service_pool.h b/src/asio_io_service_pool.h
new file mode 100644 (file)
index 0000000..f29fb4d
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// io_service_pool.hpp
+// ~~~~~~~~~~~~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_SERVER2_IO_SERVICE_POOL_HPP
+#define HTTP_SERVER2_IO_SERVICE_POOL_HPP
+
+#include "nghttp2_config.h"
+
+#include <vector>
+#include <memory>
+#include <boost/asio.hpp>
+#include <boost/noncopyable.hpp>
+#include <boost/thread.hpp>
+
+#include <nghttp2/asio_http2.h>
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+namespace server {
+
+/// A pool of io_service objects.
+class io_service_pool : private boost::noncopyable {
+public:
+  /// Construct the io_service pool.
+  explicit io_service_pool(std::size_t pool_size, std::size_t thread_pool_size);
+
+  /// Run all io_service objects in the pool.
+  void run();
+
+  /// Stop all io_service objects in the pool.
+  void stop();
+
+  /// Get an io_service to use.
+  boost::asio::io_service &get_io_service();
+
+  boost::asio::io_service &get_task_io_service();
+
+private:
+  typedef std::shared_ptr<boost::asio::io_service> io_service_ptr;
+  typedef std::shared_ptr<boost::asio::io_service::work> work_ptr;
+
+  /// The pool of io_services.
+  std::vector<io_service_ptr> io_services_;
+
+  boost::asio::io_service task_io_service_;
+  boost::thread_group thread_pool_;
+
+  /// The work that keeps the io_services running.
+  std::vector<work_ptr> work_;
+
+  /// The next io_service to use for a connection.
+  std::size_t next_io_service_;
+
+  std::size_t thread_pool_size_;
+};
+
+} // namespace server
+
+} // namespace asio_http2
+
+} // namespace nghttp2
+
+#endif // HTTP_SERVER2_IO_SERVICE_POOL_HPP
diff --git a/src/asio_server.cc b/src/asio_server.cc
new file mode 100644 (file)
index 0000000..39effab
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// server.cpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#include "asio_server.h"
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace nghttp2 {
+namespace asio_http2 {
+namespace server {
+
+server::server(const std::string &address, uint16_t port,
+               std::size_t io_service_pool_size, std::size_t thread_pool_size,
+               request_cb cb,
+               std::unique_ptr<boost::asio::ssl::context> ssl_ctx, int backlog)
+    : io_service_pool_(io_service_pool_size, thread_pool_size),
+      signals_(io_service_pool_.get_io_service()),
+      tick_timer_(io_service_pool_.get_io_service(),
+                  boost::posix_time::seconds(1)),
+      ssl_ctx_(std::move(ssl_ctx)), request_cb_(std::move(cb)) {
+  // Register to handle the signals that indicate when the server should exit.
+  // It is safe to register for the same signal multiple times in a program,
+  // provided all registration for the specified signal is made through Asio.
+  signals_.add(SIGINT);
+  signals_.add(SIGTERM);
+#if defined(SIGQUIT)
+  signals_.add(SIGQUIT);
+#endif // defined(SIGQUIT)
+  signals_.async_wait([this](const boost::system::error_code &error,
+                             int signal_number) { io_service_pool_.stop(); });
+
+  // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
+  boost::asio::ip::tcp::resolver resolver(io_service_pool_.get_io_service());
+  boost::asio::ip::tcp::resolver::query query(address, std::to_string(port));
+
+  for (auto itr = resolver.resolve(query);
+       itr != boost::asio::ip::tcp::resolver::iterator(); ++itr) {
+    boost::asio::ip::tcp::endpoint endpoint = *itr;
+    auto acceptor =
+        boost::asio::ip::tcp::acceptor(io_service_pool_.get_io_service());
+
+    acceptor.open(endpoint.protocol());
+    acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
+    acceptor.bind(endpoint);
+    if (backlog == -1) {
+      acceptor.listen();
+    } else {
+      acceptor.listen(backlog);
+    }
+    acceptors_.push_back(std::move(acceptor));
+  }
+
+  start_accept();
+
+  start_timer();
+}
+
+void server::run() { io_service_pool_.run(); }
+
+std::shared_ptr<std::string> cached_date;
+
+namespace {
+void update_date() {
+  cached_date = std::make_shared<std::string>(util::http_date(time(nullptr)));
+}
+} // namespace
+
+void server::start_timer() {
+  update_date();
+
+  tick_timer_.async_wait([this](const boost::system::error_code &e) {
+    tick_timer_.expires_at(tick_timer_.expires_at() +
+                           boost::posix_time::seconds(1));
+    start_timer();
+  });
+}
+
+typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
+
+void server::start_accept() {
+  if (ssl_ctx_) {
+    auto new_connection = std::make_shared<connection<ssl_socket>>(
+        request_cb_, io_service_pool_.get_task_io_service(),
+        io_service_pool_.get_io_service(), *ssl_ctx_);
+
+    for (auto &acceptor : acceptors_) {
+      acceptor.async_accept(
+          new_connection->socket().lowest_layer(),
+          [this, new_connection](const boost::system::error_code &e) {
+            if (!e) {
+              new_connection->socket().lowest_layer().set_option(
+                  boost::asio::ip::tcp::no_delay(true));
+              new_connection->socket().async_handshake(
+                  boost::asio::ssl::stream_base::server,
+                  [new_connection](const boost::system::error_code &e) {
+                    if (!e) {
+                      new_connection->start();
+                    }
+                  });
+            }
+
+            start_accept();
+          });
+    }
+  } else {
+    auto new_connection =
+        std::make_shared<connection<boost::asio::ip::tcp::socket>>(
+            request_cb_, io_service_pool_.get_task_io_service(),
+            io_service_pool_.get_io_service());
+
+    for (auto &acceptor : acceptors_) {
+      acceptor.async_accept(
+          new_connection->socket(),
+          [this, new_connection](const boost::system::error_code &e) {
+            if (!e) {
+              new_connection->socket().set_option(
+                  boost::asio::ip::tcp::no_delay(true));
+              new_connection->start();
+            }
+
+            start_accept();
+          });
+    }
+  }
+}
+
+} // namespace server
+} // namespace asio_http2
+} // namespace nghttp2
diff --git a/src/asio_server.h b/src/asio_server.h
new file mode 100644 (file)
index 0000000..ac10827
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+// We wrote this code based on the original code which has the
+// following license:
+//
+// server.hpp
+// ~~~~~~~~~~
+//
+// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+
+#ifndef HTTP_SERVER2_SERVER_HPP
+#define HTTP_SERVER2_SERVER_HPP
+
+#include "nghttp2_config.h"
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <boost/noncopyable.hpp>
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+#include <nghttp2/asio_http2.h>
+
+#include "asio_connection.h"
+#include "asio_io_service_pool.h"
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+namespace server {
+
+/// The top-level class of the HTTP server.
+class server : private boost::noncopyable {
+public:
+  /// Construct the server to listen on the specified TCP address and port, and
+  /// serve up files from the given directory.
+  explicit server(const std::string &address, uint16_t port,
+                  std::size_t io_service_pool_size,
+                  std::size_t thread_pool_size, request_cb cb,
+                  std::unique_ptr<boost::asio::ssl::context> ssl_ctx,
+                  int backlog = -1);
+
+  /// Run the server's io_service loop.
+  void run();
+
+private:
+  /// Initiate an asynchronous accept operation.
+  void start_accept();
+
+  void start_timer();
+
+  /// The pool of io_service objects used to perform asynchronous operations.
+  io_service_pool io_service_pool_;
+
+  /// The signal_set is used to register for process termination notifications.
+  boost::asio::signal_set signals_;
+
+  boost::asio::deadline_timer tick_timer_;
+
+  /// Acceptor used to listen for incoming connections.
+  std::vector<boost::asio::ip::tcp::acceptor> acceptors_;
+
+  std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
+
+  request_cb request_cb_;
+};
+
+} // namespace server
+
+} // namespace asio_http2
+
+} // namespace nghttp2
+
+#endif // HTTP_SERVER2_SERVER_HPP
diff --git a/src/base64.h b/src/base64.h
new file mode 100644 (file)
index 0000000..88e3add
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BASE64_H
+#define BASE64_H
+
+#include "nghttp2_config.h"
+
+#include <string>
+
+namespace nghttp2 {
+
+namespace base64 {
+
+template <typename InputIterator>
+std::string encode(InputIterator first, InputIterator last) {
+  static const char CHAR_TABLE[] = {
+      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+      'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+      'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/',
+  };
+  std::string res;
+  size_t len = last - first;
+  if (len == 0) {
+    return res;
+  }
+  size_t r = len % 3;
+  InputIterator j = last - r;
+  char temp[4];
+  while (first != j) {
+    int n = static_cast<unsigned char>(*first++) << 16;
+    n += static_cast<unsigned char>(*first++) << 8;
+    n += static_cast<unsigned char>(*first++);
+    temp[0] = CHAR_TABLE[n >> 18];
+    temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
+    temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu];
+    temp[3] = CHAR_TABLE[n & 0x3fu];
+    res.append(temp, sizeof(temp));
+  }
+  if (r == 2) {
+    int n = static_cast<unsigned char>(*first++) << 16;
+    n += static_cast<unsigned char>(*first++) << 8;
+    temp[0] = CHAR_TABLE[n >> 18];
+    temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
+    temp[2] = CHAR_TABLE[(n >> 6) & 0x3fu];
+    temp[3] = '=';
+    res.append(temp, sizeof(temp));
+  } else if (r == 1) {
+    int n = static_cast<unsigned char>(*first++) << 16;
+    temp[0] = CHAR_TABLE[n >> 18];
+    temp[1] = CHAR_TABLE[(n >> 12) & 0x3fu];
+    temp[2] = '=';
+    temp[3] = '=';
+    res.append(temp, sizeof(temp));
+  }
+  return res;
+}
+
+template <typename InputIterator>
+InputIterator getNext(InputIterator first, InputIterator last, const int *tbl) {
+  for (; first != last; ++first) {
+    if (tbl[static_cast<size_t>(*first)] != -1 || *first == '=') {
+      break;
+    }
+  }
+  return first;
+}
+
+template <typename InputIterator>
+std::string decode(InputIterator first, InputIterator last) {
+  static const int INDEX_TABLE[] = {
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
+      58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,
+      7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+      25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
+      37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+      -1, -1, -1, -1};
+  std::string res;
+  InputIterator k[4];
+  int eq = 0;
+  for (; first != last;) {
+    for (int i = 1; i <= 4; ++i) {
+      k[i - 1] = getNext(first, last, INDEX_TABLE);
+      if (k[i - 1] == last) {
+        // If i == 1, input may look like this: "TWFu\n" (i.e.,
+        // garbage at the end)
+        if (i != 1) {
+          res.clear();
+        }
+        return res;
+      } else if (*k[i - 1] == '=' && eq == 0) {
+        eq = i;
+      }
+      first = k[i - 1] + 1;
+    }
+    if (eq) {
+      break;
+    }
+    int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
+            (INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12) +
+            (INDEX_TABLE[static_cast<unsigned char>(*k[2])] << 6) +
+            INDEX_TABLE[static_cast<unsigned char>(*k[3])];
+    res += n >> 16;
+    res += n >> 8 & 0xffu;
+    res += n & 0xffu;
+  }
+  if (eq) {
+    if (eq <= 2) {
+      res.clear();
+      return res;
+    } else {
+      for (int i = eq; i <= 4; ++i) {
+        if (*k[i - 1] != '=') {
+          res.clear();
+          return res;
+        }
+      }
+      if (eq == 3) {
+        int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
+                (INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12);
+        res += n >> 16;
+      } else if (eq == 4) {
+        int n = (INDEX_TABLE[static_cast<unsigned char>(*k[0])] << 18) +
+                (INDEX_TABLE[static_cast<unsigned char>(*k[1])] << 12) +
+                (INDEX_TABLE[static_cast<unsigned char>(*k[2])] << 6);
+        res += n >> 16;
+        res += n >> 8 & 0xffu;
+      }
+    }
+  }
+  return res;
+}
+
+} // namespace base64
+
+} // namespace nghttp2
+
+#endif // BASE64_H
diff --git a/src/buffer.h b/src/buffer.h
new file mode 100644 (file)
index 0000000..e8c9a5e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BUFFER_H
+#define BUFFER_H
+
+#include "nghttp2_config.h"
+
+#include <cstring>
+#include <algorithm>
+#include <array>
+
+namespace nghttp2 {
+
+template <size_t N> struct Buffer {
+  Buffer() : pos(std::begin(buf)), last(pos) {}
+  // Returns the number of bytes to read.
+  size_t rleft() const { return last - pos; }
+  // Returns the number of bytes this buffer can store.
+  size_t wleft() const { return std::end(buf) - last; }
+  // Writes up to min(wleft(), |count|) bytes from buffer pointed by
+  // |src|.  Returns number of bytes written.
+  size_t write(const void *src, size_t count) {
+    count = std::min(count, wleft());
+    auto p = static_cast<const uint8_t *>(src);
+    last = std::copy_n(p, count, last);
+    return count;
+  }
+  size_t write(size_t count) {
+    count = std::min(count, wleft());
+    last += count;
+    return count;
+  }
+  // Drains min(rleft(), |count|) bytes from start of the buffer.
+  size_t drain(size_t count) {
+    count = std::min(count, rleft());
+    pos += count;
+    return count;
+  }
+  void reset() { pos = last = std::begin(buf); }
+  std::array<uint8_t, N> buf;
+  uint8_t *pos, *last;
+};
+
+} // namespace nghttp2
+
+#endif // BUFFER_H
diff --git a/src/buffer_test.cc b/src/buffer_test.cc
new file mode 100644 (file)
index 0000000..38688ed
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "buffer_test.h"
+
+#include <cstring>
+#include <iostream>
+#include <tuple>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "buffer.h"
+
+namespace nghttp2 {
+
+void test_buffer_write(void) {
+  Buffer<16> b;
+  CU_ASSERT(0 == b.rleft());
+  CU_ASSERT(16 == b.wleft());
+
+  b.write("012", 3);
+
+  CU_ASSERT(3 == b.rleft());
+  CU_ASSERT(13 == b.wleft());
+  CU_ASSERT(b.pos == std::begin(b.buf));
+
+  b.drain(3);
+
+  CU_ASSERT(0 == b.rleft());
+  CU_ASSERT(13 == b.wleft());
+  CU_ASSERT(3 == b.pos - std::begin(b.buf));
+
+  auto n = b.write("0123456789ABCDEF", 16);
+
+  CU_ASSERT(n == 13);
+
+  CU_ASSERT(13 == b.rleft());
+  CU_ASSERT(0 == b.wleft());
+  CU_ASSERT(3 == b.pos - std::begin(b.buf));
+  CU_ASSERT(0 == memcmp(b.pos, "0123456789ABC", 13));
+
+  b.reset();
+
+  CU_ASSERT(0 == b.rleft());
+  CU_ASSERT(16 == b.wleft());
+  CU_ASSERT(b.pos == std::begin(b.buf));
+
+  b.write(5);
+
+  CU_ASSERT(5 == b.rleft());
+  CU_ASSERT(11 == b.wleft());
+  CU_ASSERT(b.pos == std::begin(b.buf));
+}
+
+} // namespace nghttp2
diff --git a/src/buffer_test.h b/src/buffer_test.h
new file mode 100644 (file)
index 0000000..d1a889b
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef BUFFER_TEST_H
+#define BUFFER_TEST_H
+
+namespace nghttp2 {
+
+void test_buffer_write(void);
+
+} // namespace nghttp2
+
+#endif // BUFFER_TEST_H
diff --git a/src/comp_helper.c b/src/comp_helper.c
new file mode 100644 (file)
index 0000000..fe8bc2b
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "comp_helper.h"
+#include <string.h>
+
+static void dump_val(json_t *jent, const char *key, uint8_t *val, size_t len) {
+  json_object_set_new(jent, key, json_pack("s#", val, len));
+}
+
+json_t *dump_header_table(nghttp2_hd_context *context) {
+  json_t *obj, *entries;
+  size_t i;
+
+  obj = json_object();
+  entries = json_array();
+  for (i = 0; i < context->hd_table.len; ++i) {
+    nghttp2_hd_entry *ent = nghttp2_hd_table_get(context, i);
+    json_t *outent = json_object();
+    json_object_set_new(outent, "index", json_integer(i + 1));
+    dump_val(outent, "name", ent->nv.name, ent->nv.namelen);
+    dump_val(outent, "value", ent->nv.value, ent->nv.valuelen);
+    json_object_set_new(outent, "size",
+                        json_integer(ent->nv.namelen + ent->nv.valuelen +
+                                     NGHTTP2_HD_ENTRY_OVERHEAD));
+    json_array_append_new(entries, outent);
+  }
+  json_object_set_new(obj, "entries", entries);
+  json_object_set_new(obj, "size", json_integer(context->hd_table_bufsize));
+  json_object_set_new(obj, "max_size",
+                      json_integer(context->hd_table_bufsize_max));
+  return obj;
+}
+
+json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value,
+                    size_t valuelen) {
+  json_t *nv_pair = json_object();
+  char *cname = malloc(namelen + 1);
+  memcpy(cname, name, namelen);
+  cname[namelen] = '\0';
+  json_object_set_new(nv_pair, cname, json_pack("s#", value, valuelen));
+  free(cname);
+  return nv_pair;
+}
+
+json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen) {
+  json_t *headers;
+  size_t i;
+
+  headers = json_array();
+  for (i = 0; i < nvlen; ++i) {
+    json_array_append_new(headers, dump_header(nva[i].name, nva[i].namelen,
+                                               nva[i].value, nva[i].valuelen));
+  }
+  return headers;
+}
+
+void output_json_header(void) {
+  printf("{\n"
+         "  \"cases\":\n"
+         "  [\n");
+}
+
+void output_json_footer(void) {
+  printf("  ]\n"
+         "}\n");
+}
diff --git a/src/comp_helper.h b/src/comp_helper.h
new file mode 100644 (file)
index 0000000..e86defc
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_COMP_HELPER_H
+#define NGHTTP2_COMP_HELPER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <jansson.h>
+
+#include "nghttp2_hd.h"
+
+json_t *dump_header_table(nghttp2_hd_context *context);
+
+json_t *dump_header(const uint8_t *name, size_t namelen, const uint8_t *value,
+                    size_t vlauelen);
+
+json_t *dump_headers(const nghttp2_nv *nva, size_t nvlen);
+
+void output_json_header(void);
+
+void output_json_footer(void);
+
+#endif // NGHTTP2_COMP_HELPER_H
diff --git a/src/deflatehd.cc b/src/deflatehd.cc
new file mode 100644 (file)
index 0000000..dd67ebd
--- /dev/null
@@ -0,0 +1,448 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <unistd.h>
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+extern "C" {
+
+#include "nghttp2_hd.h"
+#include "nghttp2_frame.h"
+
+#include "comp_helper.h"
+}
+
+typedef struct {
+  size_t table_size;
+  size_t deflate_table_size;
+  int http1text;
+  int dump_header_table;
+} deflate_config;
+
+static deflate_config config;
+
+static size_t input_sum;
+static size_t output_sum;
+
+static char to_hex_digit(uint8_t n) {
+  if (n > 9) {
+    return n - 10 + 'a';
+  }
+  return n + '0';
+}
+
+static void to_hex(char *dest, const uint8_t *src, size_t len) {
+  size_t i;
+  for (i = 0; i < len; ++i) {
+    *dest++ = to_hex_digit(src[i] >> 4);
+    *dest++ = to_hex_digit(src[i] & 0xf);
+  }
+}
+
+static void output_to_json(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
+                           size_t inputlen, const std::vector<nghttp2_nv> &nva,
+                           int seq) {
+  auto len = nghttp2_bufs_len(bufs);
+  auto hex = std::vector<char>(len * 2);
+  auto obj = json_object();
+  auto comp_ratio = inputlen == 0 ? 0.0 : (double)len / inputlen * 100;
+
+  json_object_set_new(obj, "seq", json_integer(seq));
+  json_object_set_new(obj, "input_length", json_integer(inputlen));
+  json_object_set_new(obj, "output_length", json_integer(len));
+  json_object_set_new(obj, "percentage_of_original_size",
+                      json_real(comp_ratio));
+
+  auto hexp = hex.data();
+  for (auto ci = bufs->head; ci; ci = ci->next) {
+    auto buf = &ci->buf;
+    to_hex(hexp, buf->pos, nghttp2_buf_len(buf));
+    hexp += nghttp2_buf_len(buf);
+  }
+
+  if (len == 0) {
+    json_object_set_new(obj, "wire", json_string(""));
+  } else {
+    json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size()));
+  }
+  json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size()));
+  if (seq == 0) {
+    // We only change the header table size only once at the beginning
+    json_object_set_new(obj, "header_table_size",
+                        json_integer(config.table_size));
+  }
+  if (config.dump_header_table) {
+    json_object_set_new(obj, "header_table", dump_header_table(&deflater->ctx));
+  }
+  json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2));
+  printf("\n");
+  json_decref(obj);
+}
+
+static void deflate_hd(nghttp2_hd_deflater *deflater,
+                       const std::vector<nghttp2_nv> &nva, size_t inputlen,
+                       int seq) {
+  ssize_t rv;
+  nghttp2_bufs bufs;
+
+  nghttp2_bufs_init2(&bufs, 4096, 16, 0, nghttp2_mem_default());
+
+  rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, (nghttp2_nv *)nva.data(),
+                                  nva.size());
+  if (rv < 0) {
+    fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq);
+    exit(EXIT_FAILURE);
+  }
+
+  input_sum += inputlen;
+  output_sum += nghttp2_bufs_len(&bufs);
+
+  output_to_json(deflater, &bufs, inputlen, nva, seq);
+  nghttp2_bufs_free(&bufs);
+}
+
+static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater,
+                           int seq) {
+  size_t inputlen = 0;
+
+  auto js = json_object_get(obj, "headers");
+  if (js == nullptr) {
+    fprintf(stderr, "'headers' key is missing at %d\n", seq);
+    return -1;
+  }
+  if (!json_is_array(js)) {
+    fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq);
+    return -1;
+  }
+
+  auto len = json_array_size(js);
+  auto nva = std::vector<nghttp2_nv>(len);
+
+  for (size_t i = 0; i < len; ++i) {
+    auto nv_pair = json_array_get(js, i);
+    const char *name;
+    json_t *value;
+
+    if (!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) {
+      fprintf(stderr, "bad formatted name/value pair object at %d\n", seq);
+      return -1;
+    }
+
+    json_object_foreach(nv_pair, name, value) {
+      nva[i].name = (uint8_t *)name;
+      nva[i].namelen = strlen(name);
+
+      if (!json_is_string(value)) {
+        fprintf(stderr, "value is not string at %d\n", seq);
+        return -1;
+      }
+
+      nva[i].value = (uint8_t *)json_string_value(value);
+      nva[i].valuelen = strlen(json_string_value(value));
+
+      nva[i].flags = NGHTTP2_NV_FLAG_NONE;
+    }
+
+    inputlen += nva[i].namelen + nva[i].valuelen;
+  }
+
+  deflate_hd(deflater, nva, inputlen, seq);
+
+  return 0;
+}
+
+static nghttp2_hd_deflater *init_deflater() {
+  nghttp2_hd_deflater *deflater;
+  nghttp2_hd_deflate_new(&deflater, config.deflate_table_size);
+  nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
+  return deflater;
+}
+
+static void deinit_deflater(nghttp2_hd_deflater *deflater) {
+  nghttp2_hd_deflate_del(deflater);
+}
+
+static int perform(void) {
+  json_error_t error;
+
+  auto json = json_loadf(stdin, 0, &error);
+
+  if (json == nullptr) {
+    fprintf(stderr, "JSON loading failed\n");
+    exit(EXIT_FAILURE);
+  }
+
+  auto cases = json_object_get(json, "cases");
+
+  if (cases == nullptr) {
+    fprintf(stderr, "Missing 'cases' key in root object\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (!json_is_array(cases)) {
+    fprintf(stderr, "'cases' must be JSON array\n");
+    exit(EXIT_FAILURE);
+  }
+
+  auto deflater = init_deflater();
+  output_json_header();
+  auto len = json_array_size(cases);
+
+  for (size_t i = 0; i < len; ++i) {
+    auto obj = json_array_get(cases, i);
+    if (!json_is_object(obj)) {
+      fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
+      continue;
+    }
+    if (deflate_hd_json(obj, deflater, i) != 0) {
+      continue;
+    }
+    if (i + 1 < len) {
+      printf(",\n");
+    }
+  }
+  output_json_footer();
+  deinit_deflater(deflater);
+  json_decref(json);
+  return 0;
+}
+
+static int perform_from_http1text(void) {
+  char line[1 << 14];
+  int seq = 0;
+
+  auto deflater = init_deflater();
+  output_json_header();
+  for (;;) {
+    std::vector<nghttp2_nv> nva;
+    int end = 0;
+    size_t inputlen = 0;
+
+    for (;;) {
+      char *rv = fgets(line, sizeof(line), stdin);
+      char *val, *val_end;
+      if (rv == nullptr) {
+        end = 1;
+        break;
+      } else if (line[0] == '\n') {
+        break;
+      }
+
+      nva.emplace_back();
+      auto &nv = nva.back();
+
+      val = strchr(line + 1, ':');
+      if (val == nullptr) {
+        fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq);
+        exit(EXIT_FAILURE);
+      }
+      *val = '\0';
+      ++val;
+      for (; *val && (*val == ' ' || *val == '\t'); ++val)
+        ;
+      for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n');
+           ++val_end)
+        ;
+      *val_end = '\0';
+
+      nv.namelen = strlen(line);
+      nv.valuelen = strlen(val);
+      nv.name = (uint8_t *)strdup(line);
+      nv.value = (uint8_t *)strdup(val);
+      nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+      inputlen += nv.namelen + nv.valuelen;
+    }
+
+    if (!end) {
+      if (seq > 0) {
+        printf(",\n");
+      }
+      deflate_hd(deflater, nva, inputlen, seq);
+    }
+
+    for (auto &nv : nva) {
+      free(nv.name);
+      free(nv.value);
+    }
+
+    if (end)
+      break;
+    ++seq;
+  }
+  output_json_footer();
+  deinit_deflater(deflater);
+  return 0;
+}
+
+static void print_help(void) {
+  std::cout << R"(HPACK HTTP/2 header encoder
+Usage: deflatehd [OPTIONS] < INPUT
+
+Reads JSON data  or HTTP/1-style header fields from  stdin and outputs
+deflated header block in JSON array.
+
+For the JSON  input, the root JSON object must  contain "context" key,
+which  indicates  which  compression  context   is  used.   If  it  is
+"request", request  compression context is used.   Otherwise, response
+compression context  is used.  The  value of "cases" key  contains the
+sequence of input header set.  They share the same compression context
+and are processed in the order they appear.  Each item in the sequence
+is a JSON object  and it must have at least  "headers" key.  Its value
+is an array of a JSON object containing exactly one name/value pair.
+
+Example:
+{
+  "context": "request",
+  "cases":
+  [
+    {
+      "headers": [
+        { ":method": "GET" },
+        { ":path": "/" }
+      ]
+    },
+    {
+      "headers": [
+        { ":method": "POST" },
+        { ":path": "/" }
+      ]
+    }
+  ]
+}
+
+With  -t option,  the program  can accept  more familiar  HTTP/1 style
+header field  block.  Each header  set must  be followed by  one empty
+line:
+
+Example:
+
+:method: GET
+:scheme: https
+:path: /
+
+:method: POST
+user-agent: nghttp2
+
+The output of this program can be used as input for inflatehd.
+
+OPTIONS:
+    -t, --http1text   Use  HTTP/1 style  header field  text as  input.
+                      Each  header set  is delimited  by single  empty
+                      line.
+    -s, --table-size=<N>
+                      Set   dynamic   table   size.   In   the   HPACK
+                      specification,   this   value  is   denoted   by
+                      SETTINGS_HEADER_TABLE_SIZE.
+                      Default: 4096
+    -S, --deflate-table-size=<N>
+                      Use  first  N  bytes  of  dynamic  header  table
+                      buffer.
+                      Default: 4096
+    -d, --dump-header-table
+                      Output dynamic header table.)" << std::endl;
+}
+
+static struct option long_options[] = {
+    {"http1text", no_argument, nullptr, 't'},
+    {"table-size", required_argument, nullptr, 's'},
+    {"deflate-table-size", required_argument, nullptr, 'S'},
+    {"dump-header-table", no_argument, nullptr, 'd'},
+    {nullptr, 0, nullptr, 0}};
+
+int main(int argc, char **argv) {
+  char *end;
+
+  config.table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE;
+  config.deflate_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE;
+  config.http1text = 0;
+  config.dump_header_table = 0;
+  while (1) {
+    int option_index = 0;
+    int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index);
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+    case 'h':
+      print_help();
+      exit(EXIT_SUCCESS);
+    case 't':
+      // --http1text
+      config.http1text = 1;
+      break;
+    case 's':
+      // --table-size
+      errno = 0;
+      config.table_size = strtoul(optarg, &end, 10);
+      if (errno == ERANGE || *end != '\0') {
+        fprintf(stderr, "-s: Bad option value\n");
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'S':
+      // --deflate-table-size
+      errno = 0;
+      config.deflate_table_size = strtoul(optarg, &end, 10);
+      if (errno == ERANGE || *end != '\0') {
+        fprintf(stderr, "-S: Bad option value\n");
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'd':
+      // --dump-header-table
+      config.dump_header_table = 1;
+      break;
+    case '?':
+      exit(EXIT_FAILURE);
+    default:
+      break;
+    }
+  }
+  if (config.http1text) {
+    perform_from_http1text();
+  } else {
+    perform();
+  }
+
+  auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum;
+
+  fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum,
+          output_sum, comp_ratio);
+  return 0;
+}
diff --git a/src/h2load.cc b/src/h2load.cc
new file mode 100644 (file)
index 0000000..6cbcd16
--- /dev/null
@@ -0,0 +1,1419 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load.h"
+
+#include <getopt.h>
+#include <signal.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <cstdio>
+#include <cassert>
+#include <cstdlib>
+#include <iostream>
+#include <iomanip>
+#include <fstream>
+#include <chrono>
+#include <thread>
+#include <future>
+
+#ifdef HAVE_SPDYLAY
+#include <spdylay/spdylay.h>
+#endif // HAVE_SPDYLAY
+
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#include "http-parser/http_parser.h"
+
+#include "h2load_http2_session.h"
+#ifdef HAVE_SPDYLAY
+#include "h2load_spdy_session.h"
+#endif // HAVE_SPDYLAY
+#include "ssl.h"
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+Config::Config()
+    : addrs(nullptr), nreqs(1), nclients(1), nthreads(1),
+      max_concurrent_streams(-1), window_bits(16), connection_window_bits(16),
+      no_tls_proto(PROTO_HTTP2), port(0), default_port(0), verbose(false) {}
+
+Config::~Config() { freeaddrinfo(addrs); }
+
+Config config;
+
+namespace {
+void debug(const char *format, ...) {
+  if (config.verbose) {
+    fprintf(stderr, "[DEBUG] ");
+    va_list ap;
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+  }
+}
+} // namespace
+
+namespace {
+void debug_nextproto_error() {
+#ifdef HAVE_SPDYLAY
+  debug("no supported protocol was negotiated, expected: %s, "
+        "spdy/2, spdy/3, spdy/3.1\n",
+        NGHTTP2_PROTO_VERSION_ID);
+#else  // !HAVE_SPDYLAY
+  debug("no supported protocol was negotiated, expected: %s\n",
+        NGHTTP2_PROTO_VERSION_ID);
+#endif // !HAVE_SPDYLAY
+}
+} // namespace
+
+RequestStat::RequestStat() : completed(false) {}
+
+Stats::Stats(size_t req_todo)
+    : req_todo(0), req_started(0), req_done(0), req_success(0),
+      req_status_success(0), req_failed(0), req_error(0), bytes_total(0),
+      bytes_head(0), bytes_body(0), status(), req_stats(req_todo) {}
+
+Stream::Stream() : status_success(-1) {}
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto client = static_cast<Client *>(w->data);
+  if (client->do_read() != 0) {
+    client->fail();
+  }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto client = static_cast<Client *>(w->data);
+  auto rv = client->do_write();
+  if (rv == Client::ERR_CONNECT_FAIL) {
+    client->disconnect();
+    rv = client->connect();
+    if (rv != 0) {
+      client->fail();
+      return;
+    }
+    return;
+  }
+  if (rv != 0) {
+    client->fail();
+  }
+}
+} // namespace
+
+Client::Client(Worker *worker, size_t req_todo)
+    : worker(worker), ssl(nullptr), next_addr(config.addrs), reqidx(0),
+      state(CLIENT_IDLE), req_todo(req_todo), req_started(0), req_done(0),
+      fd(-1) {
+  ev_io_init(&wev, writecb, 0, EV_WRITE);
+  ev_io_init(&rev, readcb, 0, EV_READ);
+
+  wev.data = this;
+  rev.data = this;
+}
+
+Client::~Client() { disconnect(); }
+
+int Client::do_read() { return readfn(*this); }
+int Client::do_write() { return writefn(*this); }
+
+int Client::connect() {
+  while (next_addr) {
+    auto addr = next_addr;
+    next_addr = next_addr->ai_next;
+    fd = util::create_nonblock_socket(addr->ai_family);
+    if (fd == -1) {
+      continue;
+    }
+    if (config.scheme == "https") {
+      ssl = SSL_new(worker->ssl_ctx);
+
+      auto config = worker->config;
+
+      if (!util::numeric_host(config->host.c_str())) {
+        SSL_set_tlsext_host_name(ssl, config->host.c_str());
+      }
+
+      SSL_set_fd(ssl, fd);
+      SSL_set_connect_state(ssl);
+    }
+
+    auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
+    if (rv != 0 && errno != EINPROGRESS) {
+      if (ssl) {
+        SSL_free(ssl);
+        ssl = nullptr;
+      }
+      close(fd);
+      fd = -1;
+      continue;
+    }
+    break;
+  }
+
+  if (fd == -1) {
+    return -1;
+  }
+
+  writefn = &Client::connected;
+
+  on_readfn = &Client::on_read;
+  on_writefn = &Client::on_write;
+
+  ev_io_set(&rev, fd, EV_READ);
+  ev_io_set(&wev, fd, EV_WRITE);
+
+  ev_io_start(worker->loop, &wev);
+
+  return 0;
+}
+
+void Client::fail() {
+  process_abandoned_streams();
+
+  disconnect();
+}
+
+void Client::disconnect() {
+  streams.clear();
+  session.reset();
+  state = CLIENT_IDLE;
+  ev_io_stop(worker->loop, &wev);
+  ev_io_stop(worker->loop, &rev);
+  if (ssl) {
+    SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
+    ERR_clear_error();
+    SSL_shutdown(ssl);
+    SSL_free(ssl);
+    ssl = nullptr;
+  }
+  if (fd != -1) {
+    shutdown(fd, SHUT_WR);
+    close(fd);
+    fd = -1;
+  }
+}
+
+void Client::submit_request() {
+  auto req_stat = &worker->stats.req_stats[worker->stats.req_started++];
+  session->submit_request(req_stat);
+  ++req_started;
+}
+
+void Client::process_abandoned_streams() {
+  auto req_abandoned = req_todo - req_done;
+
+  worker->stats.req_failed += req_abandoned;
+  worker->stats.req_error += req_abandoned;
+  worker->stats.req_done += req_abandoned;
+
+  req_done = req_todo;
+}
+
+void Client::report_progress() {
+  if (worker->id == 0 &&
+      worker->stats.req_done % worker->progress_interval == 0) {
+    std::cout << "progress: "
+              << worker->stats.req_done * 100 / worker->stats.req_todo
+              << "% done" << std::endl;
+  }
+}
+
+namespace {
+const char *get_tls_protocol(SSL *ssl) {
+  auto session = SSL_get_session(ssl);
+
+  switch (session->ssl_version) {
+  case SSL2_VERSION:
+    return "SSLv2";
+  case SSL3_VERSION:
+    return "SSLv3";
+  case TLS1_2_VERSION:
+    return "TLSv1.2";
+  case TLS1_1_VERSION:
+    return "TLSv1.1";
+  case TLS1_VERSION:
+    return "TLSv1";
+  default:
+    return "unknown";
+  }
+}
+} // namespace
+
+namespace {
+void print_server_tmp_key(SSL *ssl) {
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+  EVP_PKEY *key;
+
+  if (!SSL_get_server_tmp_key(ssl, &key)) {
+    return;
+  }
+
+  auto key_del = defer(EVP_PKEY_free, key);
+
+  std::cout << "Server Temp Key: ";
+
+  switch (EVP_PKEY_id(key)) {
+  case EVP_PKEY_RSA:
+    std::cout << "RSA " << EVP_PKEY_bits(key) << " bits" << std::endl;
+    break;
+  case EVP_PKEY_DH:
+    std::cout << "DH " << EVP_PKEY_bits(key) << " bits" << std::endl;
+    break;
+  case EVP_PKEY_EC: {
+    auto ec = EVP_PKEY_get1_EC_KEY(key);
+    auto ec_del = defer(EC_KEY_free, ec);
+    auto nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
+    auto cname = EC_curve_nid2nist(nid);
+    if (!cname) {
+      cname = OBJ_nid2sn(nid);
+    }
+
+    std::cout << "ECDH " << cname << " " << EVP_PKEY_bits(key) << " bits"
+              << std::endl;
+    break;
+  }
+  }
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+}
+} // namespace
+
+void Client::report_tls_info() {
+  if (worker->id == 0 && !worker->tls_info_report_done) {
+    worker->tls_info_report_done = true;
+    auto cipher = SSL_get_current_cipher(ssl);
+    std::cout << "Protocol: " << get_tls_protocol(ssl) << "\n"
+              << "Cipher: " << SSL_CIPHER_get_name(cipher) << std::endl;
+    print_server_tmp_key(ssl);
+  }
+}
+
+void Client::terminate_session() { session->terminate(); }
+
+void Client::on_request(int32_t stream_id) { streams[stream_id] = Stream(); }
+
+void Client::on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen) {
+  auto itr = streams.find(stream_id);
+  if (itr == std::end(streams)) {
+    return;
+  }
+  auto &stream = (*itr).second;
+  if (stream.status_success == -1 && namelen == 7 &&
+      util::streq(":status", 7, name, namelen)) {
+    int status = 0;
+    for (size_t i = 0; i < valuelen; ++i) {
+      if ('0' <= value[i] && value[i] <= '9') {
+        status *= 10;
+        status += value[i] - '0';
+        if (status > 999) {
+          stream.status_success = 0;
+          return;
+        }
+      } else {
+        break;
+      }
+    }
+
+    if (status >= 200 && status < 300) {
+      ++worker->stats.status[2];
+      stream.status_success = 1;
+    } else if (status < 400) {
+      ++worker->stats.status[3];
+      stream.status_success = 1;
+    } else if (status < 600) {
+      ++worker->stats.status[status / 100];
+      stream.status_success = 0;
+    } else {
+      stream.status_success = 0;
+    }
+  }
+}
+
+void Client::on_stream_close(int32_t stream_id, bool success,
+                             RequestStat *req_stat) {
+  req_stat->stream_close_time = std::chrono::steady_clock::now();
+  if (success) {
+    req_stat->completed = true;
+    ++worker->stats.req_success;
+  }
+  ++worker->stats.req_done;
+  ++req_done;
+  if (success && streams[stream_id].status_success == 1) {
+    ++worker->stats.req_status_success;
+  } else {
+    ++worker->stats.req_failed;
+  }
+  report_progress();
+  streams.erase(stream_id);
+  if (req_done == req_todo) {
+    terminate_session();
+    return;
+  }
+
+  if (req_started < req_todo) {
+    submit_request();
+    return;
+  }
+}
+
+int Client::noop() { return 0; }
+
+int Client::on_connect() {
+  if (ssl) {
+    report_tls_info();
+
+    const unsigned char *next_proto = nullptr;
+    unsigned int next_proto_len;
+    SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
+    for (int i = 0; i < 2; ++i) {
+      if (next_proto) {
+        if (util::check_h2_is_selected(next_proto, next_proto_len)) {
+          session = make_unique<Http2Session>(this);
+        } else {
+#ifdef HAVE_SPDYLAY
+          auto spdy_version =
+              spdylay_npn_get_version(next_proto, next_proto_len);
+          if (spdy_version) {
+            session = make_unique<SpdySession>(this, spdy_version);
+          } else {
+            debug_nextproto_error();
+            fail();
+            return -1;
+          }
+#else  // !HAVE_SPDYLAY
+          debug_nextproto_error();
+          fail();
+          return -1;
+#endif // !HAVE_SPDYLAY
+        }
+      }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+      SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
+#else  // OPENSSL_VERSION_NUMBER < 0x10002000L
+      break;
+#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
+    }
+
+    if (!next_proto) {
+      debug_nextproto_error();
+      fail();
+      return -1;
+    }
+  } else {
+    switch (config.no_tls_proto) {
+    case Config::PROTO_HTTP2:
+      session = make_unique<Http2Session>(this);
+      break;
+#ifdef HAVE_SPDYLAY
+    case Config::PROTO_SPDY2:
+      session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY2);
+      break;
+    case Config::PROTO_SPDY3:
+      session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3);
+      break;
+    case Config::PROTO_SPDY3_1:
+      session = make_unique<SpdySession>(this, SPDYLAY_PROTO_SPDY3_1);
+      break;
+#endif // HAVE_SPDYLAY
+    default:
+      // unreachable
+      assert(0);
+    }
+  }
+
+  state = CLIENT_CONNECTED;
+
+  session->on_connect();
+
+  auto nreq =
+      std::min(req_todo - req_started, (size_t)config.max_concurrent_streams);
+
+  for (; nreq > 0; --nreq) {
+    submit_request();
+  }
+
+  signal_write();
+
+  return 0;
+}
+
+int Client::on_read(const uint8_t *data, size_t len) {
+  auto rv = session->on_read(data, len);
+  if (rv != 0) {
+    return -1;
+  }
+  worker->stats.bytes_total += len;
+  signal_write();
+  return 0;
+}
+
+int Client::on_write() {
+  if (session->on_write() != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+int Client::read_clear() {
+  uint8_t buf[8192];
+
+  for (;;) {
+    ssize_t nread;
+    while ((nread = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR)
+      ;
+    if (nread == -1) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        return 0;
+      }
+      return -1;
+    }
+
+    if (nread == 0) {
+      return -1;
+    }
+
+    if (on_read(buf, nread) != 0) {
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int Client::write_clear() {
+  for (;;) {
+    if (wb.rleft() > 0) {
+      ssize_t nwrite;
+      while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
+        ;
+      if (nwrite == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          ev_io_start(worker->loop, &wev);
+          return 0;
+        }
+        return -1;
+      }
+      wb.drain(nwrite);
+      continue;
+    }
+
+    if (on_write() != 0) {
+      return -1;
+    }
+    if (wb.rleft() == 0) {
+      wb.reset();
+      break;
+    }
+  }
+
+  ev_io_stop(worker->loop, &wev);
+
+  return 0;
+}
+
+int Client::connected() {
+  if (!util::check_socket_connected(fd)) {
+    return ERR_CONNECT_FAIL;
+  }
+  ev_io_start(worker->loop, &rev);
+  ev_io_stop(worker->loop, &wev);
+
+  if (ssl) {
+    readfn = &Client::tls_handshake;
+    writefn = &Client::tls_handshake;
+
+    return do_write();
+  }
+
+  readfn = &Client::read_clear;
+  writefn = &Client::write_clear;
+
+  if (on_connect() != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int Client::tls_handshake() {
+  ERR_clear_error();
+
+  auto rv = SSL_do_handshake(ssl);
+
+  if (rv == 0) {
+    return -1;
+  }
+
+  if (rv < 0) {
+    auto err = SSL_get_error(ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      ev_io_stop(worker->loop, &wev);
+      return 0;
+    case SSL_ERROR_WANT_WRITE:
+      ev_io_start(worker->loop, &wev);
+      return 0;
+    default:
+      return -1;
+    }
+  }
+
+  ev_io_stop(worker->loop, &wev);
+
+  readfn = &Client::read_tls;
+  writefn = &Client::write_tls;
+
+  if (on_connect() != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int Client::read_tls() {
+  uint8_t buf[8192];
+
+  ERR_clear_error();
+
+  for (;;) {
+    auto rv = SSL_read(ssl, buf, sizeof(buf));
+
+    if (rv == 0) {
+      return -1;
+    }
+
+    if (rv < 0) {
+      auto err = SSL_get_error(ssl, rv);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+        return 0;
+      case SSL_ERROR_WANT_WRITE:
+        // renegotiation started
+        return -1;
+      default:
+        return -1;
+      }
+    }
+
+    if (on_read(buf, rv) != 0) {
+      return -1;
+    }
+  }
+}
+
+int Client::write_tls() {
+  ERR_clear_error();
+
+  for (;;) {
+    if (wb.rleft() > 0) {
+      auto rv = SSL_write(ssl, wb.pos, wb.rleft());
+
+      if (rv == 0) {
+        return -1;
+      }
+
+      if (rv < 0) {
+        auto err = SSL_get_error(ssl, rv);
+        switch (err) {
+        case SSL_ERROR_WANT_READ:
+          // renegotiation started
+          return -1;
+        case SSL_ERROR_WANT_WRITE:
+          ev_io_start(worker->loop, &wev);
+          return 0;
+        default:
+          return -1;
+        }
+      }
+
+      wb.drain(rv);
+
+      continue;
+    }
+    if (on_write() != 0) {
+      return -1;
+    }
+    if (wb.rleft() == 0) {
+      break;
+    }
+  }
+
+  ev_io_stop(worker->loop, &wev);
+
+  return 0;
+}
+
+void Client::record_request_time(RequestStat *req_stat) {
+  req_stat->request_time = std::chrono::steady_clock::now();
+}
+
+void Client::signal_write() { ev_io_start(worker->loop, &wev); }
+
+Worker::Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t req_todo, size_t nclients,
+               Config *config)
+    : stats(req_todo), loop(ev_loop_new(0)), ssl_ctx(ssl_ctx), config(config),
+      id(id), tls_info_report_done(false) {
+  stats.req_todo = req_todo;
+  progress_interval = std::max((size_t)1, req_todo / 10);
+
+  auto nreqs_per_client = req_todo / nclients;
+  auto nreqs_rem = req_todo % nclients;
+
+  for (size_t i = 0; i < nclients; ++i) {
+    auto req_todo = nreqs_per_client;
+    if (nreqs_rem > 0) {
+      ++req_todo;
+      --nreqs_rem;
+    }
+    clients.push_back(make_unique<Client>(this, req_todo));
+  }
+}
+
+Worker::~Worker() {
+  // first clear clients so that io watchers are stopped before
+  // destructing ev_loop.
+  clients.clear();
+  ev_loop_destroy(loop);
+}
+
+void Worker::run() {
+  for (auto &client : clients) {
+    if (client->connect() != 0) {
+      std::cerr << "client could not connect to host" << std::endl;
+      client->fail();
+    }
+  }
+  ev_run(loop, 0);
+}
+
+namespace {
+double within_sd(const std::vector<std::unique_ptr<Worker>> &workers,
+                 const std::chrono::microseconds &mean,
+                 const std::chrono::microseconds &sd, size_t n) {
+  auto upper = mean.count() + sd.count();
+  auto lower = mean.count() - sd.count();
+  size_t m = 0;
+  for (const auto &w : workers) {
+    for (const auto &req_stat : w->stats.req_stats) {
+      if (!req_stat.completed) {
+        continue;
+      }
+      auto t = std::chrono::duration_cast<std::chrono::microseconds>(
+          req_stat.stream_close_time - req_stat.request_time);
+      if (lower <= t.count() && t.count() <= upper) {
+        ++m;
+      }
+    }
+  }
+  return (m / static_cast<double>(n)) * 100;
+}
+} // namespace
+
+namespace {
+TimeStats
+process_time_stats(const std::vector<std::unique_ptr<Worker>> &workers) {
+  auto ts = TimeStats();
+  int64_t sum = 0;
+  size_t n = 0;
+
+  ts.time_min = std::chrono::microseconds::max();
+  ts.time_max = std::chrono::microseconds::min();
+  ts.within_sd = 0.;
+
+  // standard deviation calculated using Rapid calculation method:
+  // http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
+  double a = 0, q = 0;
+  for (const auto &w : workers) {
+    for (const auto &req_stat : w->stats.req_stats) {
+      if (!req_stat.completed) {
+        continue;
+      }
+      ++n;
+      auto t = std::chrono::duration_cast<std::chrono::microseconds>(
+          req_stat.stream_close_time - req_stat.request_time);
+      ts.time_min = std::min(ts.time_min, t);
+      ts.time_max = std::max(ts.time_max, t);
+      sum += t.count();
+
+      auto na = a + (t.count() - a) / n;
+      q = q + (t.count() - a) * (t.count() - na);
+      a = na;
+    }
+  }
+  if (n == 0) {
+    ts.time_max = ts.time_min = std::chrono::microseconds::zero();
+    return ts;
+  }
+
+  ts.time_mean = std::chrono::microseconds(sum / n);
+  ts.time_sd = std::chrono::microseconds(
+      static_cast<std::chrono::microseconds::rep>(sqrt(q / n)));
+
+  ts.within_sd = within_sd(workers, ts.time_mean, ts.time_sd, n);
+  return ts;
+}
+} // namespace
+
+namespace {
+void resolve_host() {
+  int rv;
+  addrinfo hints, *res;
+
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+  hints.ai_flags = AI_ADDRCONFIG;
+
+  rv = getaddrinfo(config.host.c_str(), util::utos(config.port).c_str(), &hints,
+                   &res);
+  if (rv != 0) {
+    std::cerr << "getaddrinfo() failed: " << gai_strerror(rv) << std::endl;
+    exit(EXIT_FAILURE);
+  }
+  if (res == nullptr) {
+    std::cerr << "No address returned" << std::endl;
+    exit(EXIT_FAILURE);
+  }
+  config.addrs = res;
+}
+} // namespace
+
+namespace {
+std::string get_reqline(const char *uri, const http_parser_url &u) {
+  std::string reqline;
+
+  if (util::has_uri_field(u, UF_PATH)) {
+    reqline = util::get_uri_field(uri, u, UF_PATH);
+  } else {
+    reqline = "/";
+  }
+
+  if (util::has_uri_field(u, UF_QUERY)) {
+    reqline += "?";
+    reqline += util::get_uri_field(uri, u, UF_QUERY);
+  }
+
+  return reqline;
+}
+} // namespace
+
+namespace {
+int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
+                                unsigned char *outlen, const unsigned char *in,
+                                unsigned int inlen, void *arg) {
+  if (util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
+                      inlen)) {
+    return SSL_TLSEXT_ERR_OK;
+  }
+#ifdef HAVE_SPDYLAY
+  if (spdylay_select_next_protocol(out, outlen, in, inlen) > 0) {
+    return SSL_TLSEXT_ERR_OK;
+  }
+#endif
+  return SSL_TLSEXT_ERR_NOACK;
+}
+} // namespace
+
+namespace {
+template <typename Iterator>
+std::vector<std::string> parse_uris(Iterator first, Iterator last) {
+  std::vector<std::string> reqlines;
+
+  // First URI is treated specially.  We use scheme, host and port of
+  // this URI and ignore those in the remaining URIs if present.
+  http_parser_url u;
+  memset(&u, 0, sizeof(u));
+
+  if (first == last) {
+    std::cerr << "no URI available" << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  auto uri = (*first).c_str();
+  ++first;
+
+  if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0 ||
+      !util::has_uri_field(u, UF_SCHEMA) || !util::has_uri_field(u, UF_HOST)) {
+    std::cerr << "invalid URI: " << uri << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  config.scheme = util::get_uri_field(uri, u, UF_SCHEMA);
+  config.host = util::get_uri_field(uri, u, UF_HOST);
+  config.default_port = util::get_default_port(uri, u);
+  if (util::has_uri_field(u, UF_PORT)) {
+    config.port = u.port;
+  } else {
+    config.port = config.default_port;
+  }
+
+  reqlines.push_back(get_reqline(uri, u));
+
+  for (; first != last; ++first) {
+    http_parser_url u;
+    memset(&u, 0, sizeof(u));
+
+    auto uri = (*first).c_str();
+
+    if (http_parser_parse_url(uri, strlen(uri), 0, &u) != 0) {
+      std::cerr << "invalid URI: " << uri << std::endl;
+      exit(EXIT_FAILURE);
+    }
+
+    reqlines.push_back(get_reqline(uri, u));
+  }
+
+  return reqlines;
+}
+} // namespace
+
+namespace {
+std::vector<std::string> read_uri_from_file(std::istream &infile) {
+  std::vector<std::string> uris;
+  std::string line_uri;
+  while (std::getline(infile, line_uri)) {
+    uris.push_back(line_uri);
+  }
+
+  return uris;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+  out << "h2load nghttp2/" NGHTTP2_VERSION << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+  out << R"(Usage: h2load [OPTIONS]... [URI]...
+benchmarking tool for HTTP/2 and SPDY server)" << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+  print_usage(out);
+
+  out << R"(
+  <URI>       Specify URI to access.   Multiple URIs can be specified.
+              URIs are used  in this order for each  client.  All URIs
+              are used, then  first URI is used and then  2nd URI, and
+              so  on.  The  scheme, host  and port  in the  subsequent
+              URIs, if present,  are ignored.  Those in  the first URI
+              are used solely.
+Options:
+  -n, --requests=<N>
+              Number of requests.
+              Default: )" << config.nreqs << R"(
+  -c, --clients=<N>
+              Number of concurrent clients.
+              Default: )" << config.nclients << R"(
+  -t, --threads=<N>
+              Number of native threads.
+              Default: )" << config.nthreads << R"(
+  -i, --input-file=<FILE>
+              Path of a file with multiple URIs are seperated by EOLs.
+              This option will disable URIs getting from command-line.
+              If '-' is given as <FILE>, URIs will be read from stdin.
+              URIs are used  in this order for each  client.  All URIs
+              are used, then  first URI is used and then  2nd URI, and
+              so  on.  The  scheme, host  and port  in the  subsequent
+              URIs, if present,  are ignored.  Those in  the first URI
+              are used solely.
+  -m, --max-concurrent-streams=(auto|<N>)
+              Max concurrent streams to  issue per session.  If "auto"
+              is given, the number of given URIs is used.
+              Default: auto
+  -w, --window-bits=<N>
+              Sets the stream level initial window size to (2**<N>)-1.
+              For SPDY, 2**<N> is used instead.
+  -W, --connection-window-bits=<N>
+              Sets  the  connection  level   initial  window  size  to
+              (2**<N>)-1.  For SPDY, if <N>  is strictly less than 16,
+              this option  is ignored.   Otherwise 2**<N> is  used for
+              SPDY.
+  -H, --header=<HEADER>
+              Add/Override a header to the requests.
+  -p, --no-tls-proto=<PROTOID>
+              Specify ALPN identifier of the  protocol to be used when
+              accessing http URI without SSL/TLS.)";
+#ifdef HAVE_SPDYLAY
+  out << R"(
+              Available protocols: spdy/2, spdy/3, spdy/3.1 and )";
+#else  // !HAVE_SPDYLAY
+  out << R"(
+              Available protocol: )";
+#endif // !HAVE_SPDYLAY
+  out << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
+              Default: )" << NGHTTP2_CLEARTEXT_PROTO_VERSION_ID << R"(
+  -v, --verbose
+              Output debug information.
+  --version   Display version information and exit.
+  -h, --help  Display this help and exit.)" << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+  while (1) {
+    static int flag = 0;
+    static option long_options[] = {
+        {"requests", required_argument, nullptr, 'n'},
+        {"clients", required_argument, nullptr, 'c'},
+        {"threads", required_argument, nullptr, 't'},
+        {"max-concurrent-streams", required_argument, nullptr, 'm'},
+        {"window-bits", required_argument, nullptr, 'w'},
+        {"connection-window-bits", required_argument, nullptr, 'W'},
+        {"input-file", required_argument, nullptr, 'i'},
+        {"header", required_argument, nullptr, 'H'},
+        {"no-tls-proto", required_argument, nullptr, 'p'},
+        {"verbose", no_argument, nullptr, 'v'},
+        {"help", no_argument, nullptr, 'h'},
+        {"version", no_argument, &flag, 1},
+        {nullptr, 0, nullptr, 0}};
+    int option_index = 0;
+    auto c = getopt_long(argc, argv, "hvW:c:m:n:p:t:w:H:i:", long_options,
+                         &option_index);
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+    case 'n':
+      config.nreqs = strtoul(optarg, nullptr, 10);
+      break;
+    case 'c':
+      config.nclients = strtoul(optarg, nullptr, 10);
+      break;
+    case 't':
+#ifdef NOTHREADS
+      std::cerr << "-t: WARNING: Threading disabled at build time, "
+                << "no threads created." << std::endl;
+#else
+      config.nthreads = strtoul(optarg, nullptr, 10);
+#endif // NOTHREADS
+      break;
+    case 'm':
+      if (util::strieq("auto", optarg)) {
+        config.max_concurrent_streams = -1;
+      } else {
+        config.max_concurrent_streams = strtoul(optarg, nullptr, 10);
+      }
+      break;
+    case 'w':
+    case 'W': {
+      errno = 0;
+      char *endptr = nullptr;
+      auto n = strtoul(optarg, &endptr, 10);
+      if (errno == 0 && *endptr == '\0' && n < 31) {
+        if (c == 'w') {
+          config.window_bits = n;
+        } else {
+          config.connection_window_bits = n;
+        }
+      } else {
+        std::cerr << "-" << static_cast<char>(c)
+                  << ": specify the integer in the range [0, 30], inclusive"
+                  << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      break;
+    }
+    case 'H': {
+      char *header = optarg;
+      // Skip first possible ':' in the header name
+      char *value = strchr(optarg + 1, ':');
+      if (!value || (header[0] == ':' && header + 1 == value)) {
+        std::cerr << "-H: invalid header: " << optarg << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      *value = 0;
+      value++;
+      while (isspace(*value)) {
+        value++;
+      }
+      if (*value == 0) {
+        // This could also be a valid case for suppressing a header
+        // similar to curl
+        std::cerr << "-H: invalid header - value missing: " << optarg
+                  << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      // Note that there is no processing currently to handle multiple
+      // message-header fields with the same field name
+      config.custom_headers.emplace_back(header, value);
+      util::inp_strlower(config.custom_headers.back().name);
+      break;
+    }
+    case 'i': {
+      config.ifile = std::string(optarg);
+      break;
+    }
+    case 'p':
+      if (util::strieq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, optarg)) {
+        config.no_tls_proto = Config::PROTO_HTTP2;
+#ifdef HAVE_SPDYLAY
+      } else if (util::strieq("spdy/2", optarg)) {
+        config.no_tls_proto = Config::PROTO_SPDY2;
+      } else if (util::strieq("spdy/3", optarg)) {
+        config.no_tls_proto = Config::PROTO_SPDY3;
+      } else if (util::strieq("spdy/3.1", optarg)) {
+        config.no_tls_proto = Config::PROTO_SPDY3_1;
+#endif // HAVE_SPDYLAY
+      } else {
+        std::cerr << "-p: unsupported protocol " << optarg << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'v':
+      config.verbose = true;
+      break;
+    case 'h':
+      print_help(std::cout);
+      exit(EXIT_SUCCESS);
+    case '?':
+      util::show_candidates(argv[optind - 1], long_options);
+      exit(EXIT_FAILURE);
+    case 0:
+      switch (flag) {
+      case 1:
+        // version option
+        print_version(std::cout);
+        exit(EXIT_SUCCESS);
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (argc == optind) {
+    if (config.ifile.empty()) {
+      std::cerr << "no URI or input file given" << std::endl;
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (config.nreqs == 0) {
+    std::cerr << "-n: the number of requests must be strictly greater than 0."
+              << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  if (config.max_concurrent_streams == 0) {
+    std::cerr << "-m: the max concurrent streams must be strictly greater "
+              << "than 0." << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  if (config.nthreads == 0) {
+    std::cerr << "-t: the number of threads must be strictly greater than 0."
+              << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  if (config.nreqs < config.nclients) {
+    std::cerr << "-n, -c: the number of requests must be greater than or "
+              << "equal to the concurrent clients." << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  if (config.nthreads > std::thread::hardware_concurrency()) {
+    std::cerr << "-t: warning: the number of threads is greater than hardware "
+              << "cores." << std::endl;
+  }
+
+  struct sigaction act;
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, nullptr);
+  OPENSSL_config(nullptr);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+
+#ifndef NOTHREADS
+  ssl::LibsslGlobalLock lock;
+#endif // NOTHREADS
+
+  auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+  if (!ssl_ctx) {
+    std::cerr << "Failed to create SSL_CTX: "
+              << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  SSL_CTX_set_options(ssl_ctx,
+                      SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                          SSL_OP_NO_COMPRESSION |
+                          SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+  if (SSL_CTX_set_cipher_list(ssl_ctx, ssl::DEFAULT_CIPHER_LIST) == 0) {
+    std::cerr << "SSL_CTX_set_cipher_list failed: "
+              << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
+                                   nullptr);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+  auto proto_list = util::get_default_alpn();
+#ifdef HAVE_SPDYLAY
+  static const char spdy_proto_list[] = "\x8spdy/3.1\x6spdy/3\x6spdy/2";
+  std::copy_n(spdy_proto_list, sizeof(spdy_proto_list) - 1,
+              std::back_inserter(proto_list));
+#endif // HAVE_SPDYLAY
+  SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+
+  std::vector<std::string> reqlines;
+
+  if (config.ifile.empty()) {
+    std::vector<std::string> uris;
+    std::copy(&argv[optind], &argv[argc], std::back_inserter(uris));
+    reqlines = parse_uris(std::begin(uris), std::end(uris));
+  } else {
+    std::vector<std::string> uris;
+    if (config.ifile == "-") {
+      uris = read_uri_from_file(std::cin);
+    } else {
+      std::ifstream infile(config.ifile);
+      if (!infile) {
+        std::cerr << "cannot read input file: " << config.ifile << std::endl;
+        exit(EXIT_FAILURE);
+      }
+
+      uris = read_uri_from_file(infile);
+    }
+
+    reqlines = parse_uris(std::begin(uris), std::end(uris));
+  }
+
+  if (config.max_concurrent_streams == -1) {
+    config.max_concurrent_streams = reqlines.size();
+  }
+
+  Headers shared_nva;
+  shared_nva.emplace_back(":scheme", config.scheme);
+  if (config.port != config.default_port) {
+    shared_nva.emplace_back(":authority",
+                            config.host + ":" + util::utos(config.port));
+  } else {
+    shared_nva.emplace_back(":authority", config.host);
+  }
+  shared_nva.emplace_back(":method", "GET");
+
+  // list overridalbe headers
+  auto override_hdrs =
+      make_array<std::string>(":authority", ":host", ":method", ":scheme");
+
+  for (auto &kv : config.custom_headers) {
+    if (std::find(std::begin(override_hdrs), std::end(override_hdrs),
+                  kv.name) != std::end(override_hdrs)) {
+      // override header
+      for (auto &nv : shared_nva) {
+        if ((nv.name == ":authority" && kv.name == ":host") ||
+            (nv.name == kv.name)) {
+          nv.value = kv.value;
+        }
+      }
+    } else {
+      // add additional headers
+      shared_nva.push_back(kv);
+    }
+  }
+
+  for (auto &req : reqlines) {
+    // For nghttp2
+    std::vector<nghttp2_nv> nva;
+
+    nva.push_back(http2::make_nv_ls(":path", req));
+
+    for (auto &nv : shared_nva) {
+      nva.push_back(http2::make_nv(nv.name, nv.value, false));
+    }
+
+    config.nva.push_back(std::move(nva));
+
+    // For spdylay
+    std::vector<const char *> cva;
+
+    cva.push_back(":path");
+    cva.push_back(req.c_str());
+
+    for (auto &nv : shared_nva) {
+      if (nv.name == ":authority") {
+        cva.push_back(":host");
+      } else {
+        cva.push_back(nv.name.c_str());
+      }
+      cva.push_back(nv.value.c_str());
+    }
+    cva.push_back(":version");
+    cva.push_back("HTTP/1.1");
+    cva.push_back(nullptr);
+
+    config.nv.push_back(std::move(cva));
+  }
+
+  resolve_host();
+
+  if (config.nclients == 1) {
+    config.nthreads = 1;
+  }
+
+  size_t nreqs_per_thread = config.nreqs / config.nthreads;
+  ssize_t nreqs_rem = config.nreqs % config.nthreads;
+
+  size_t nclients_per_thread = config.nclients / config.nthreads;
+  ssize_t nclients_rem = config.nclients % config.nthreads;
+
+  std::cout << "starting benchmark..." << std::endl;
+
+  auto start = std::chrono::steady_clock::now();
+
+  std::vector<std::unique_ptr<Worker>> workers;
+  workers.reserve(config.nthreads - 1);
+
+#ifndef NOTHREADS
+  std::vector<std::future<void>> futures;
+  for (size_t i = 0; i < config.nthreads - 1; ++i) {
+    auto nreqs = nreqs_per_thread + (nreqs_rem-- > 0);
+    auto nclients = nclients_per_thread + (nclients_rem-- > 0);
+    std::cout << "spawning thread #" << i << ": " << nclients
+              << " concurrent clients, " << nreqs << " total requests"
+              << std::endl;
+    workers.push_back(
+        make_unique<Worker>(i, ssl_ctx, nreqs, nclients, &config));
+    auto &worker = workers.back();
+    futures.push_back(
+        std::async(std::launch::async, [&worker]() { worker->run(); }));
+  }
+#endif // NOTHREADS
+
+  auto nreqs_last = nreqs_per_thread + (nreqs_rem-- > 0);
+  auto nclients_last = nclients_per_thread + (nclients_rem-- > 0);
+  std::cout << "spawning thread #" << (config.nthreads - 1) << ": "
+            << nclients_last << " concurrent clients, " << nreqs_last
+            << " total requests" << std::endl;
+  workers.push_back(make_unique<Worker>(config.nthreads - 1, ssl_ctx,
+                                        nreqs_last, nclients_last, &config));
+  workers.back()->run();
+
+#ifndef NOTHREADS
+  for (auto &fut : futures) {
+    fut.get();
+  }
+#endif // NOTHREADS
+
+  auto end = std::chrono::steady_clock::now();
+  auto duration =
+      std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+
+  Stats stats(0);
+  for (const auto &w : workers) {
+    const auto &s = w->stats;
+
+    stats.req_todo += s.req_todo;
+    stats.req_started += s.req_started;
+    stats.req_done += s.req_done;
+    stats.req_success += s.req_success;
+    stats.req_status_success += s.req_status_success;
+    stats.req_failed += s.req_failed;
+    stats.req_error += s.req_error;
+    stats.bytes_total += s.bytes_total;
+    stats.bytes_head += s.bytes_head;
+    stats.bytes_body += s.bytes_body;
+
+    for (size_t i = 0; i < stats.status.size(); ++i) {
+      stats.status[i] += s.status[i];
+    }
+  }
+
+  auto time_stats = process_time_stats(workers);
+
+  // Requests which have not been issued due to connection errors, are
+  // counted towards req_failed and req_error.
+  auto req_not_issued =
+      stats.req_todo - stats.req_status_success - stats.req_failed;
+  stats.req_failed += req_not_issued;
+  stats.req_error += req_not_issued;
+
+  // UI is heavily inspired by weighttp[1] and wrk[2]
+  //
+  // [1] https://github.com/lighttpd/weighttp
+  // [2] https://github.com/wg/wrk
+  size_t rps = 0;
+  int64_t bps = 0;
+  if (duration.count() > 0) {
+    auto secd = static_cast<double>(duration.count()) / (1000 * 1000);
+    rps = stats.req_success / secd;
+    bps = stats.bytes_total / secd;
+  }
+
+  std::cout << R"(
+finished in )" << util::format_duration(duration) << ", " << rps << " req/s, "
+            << util::utos_with_funit(bps) << R"(B/s
+requests: )" << stats.req_todo << " total, " << stats.req_started
+            << " started, " << stats.req_done << " done, "
+            << stats.req_status_success << " succeeded, " << stats.req_failed
+            << " failed, " << stats.req_error << R"( errored
+status codes: )" << stats.status[2] << " 2xx, " << stats.status[3] << " 3xx, "
+            << stats.status[4] << " 4xx, " << stats.status[5] << R"( 5xx
+traffic: )" << stats.bytes_total << " bytes total, " << stats.bytes_head
+            << " bytes headers, " << stats.bytes_body << R"( bytes data
+                     min         max         mean         sd        +/- sd
+time for request: )" << std::setw(10)
+            << util::format_duration(time_stats.time_min) << "  "
+            << std::setw(10) << util::format_duration(time_stats.time_max)
+            << "  " << std::setw(10)
+            << util::format_duration(time_stats.time_mean) << "  "
+            << std::setw(10) << util::format_duration(time_stats.time_sd)
+            << std::setw(9) << util::dtos(time_stats.within_sd) << "%"
+            << std::endl;
+
+  SSL_CTX_free(ssl_ctx);
+
+  return 0;
+}
+
+} // namespace h2load
+
+int main(int argc, char **argv) { return h2load::main(argc, argv); }
diff --git a/src/h2load.h b/src/h2load.h
new file mode 100644 (file)
index 0000000..7ba6e95
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_H
+#define H2LOAD_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <vector>
+#include <string>
+#include <unordered_map>
+#include <memory>
+#include <chrono>
+#include <array>
+
+#include <nghttp2/nghttp2.h>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "http2.h"
+#include "buffer.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+class Session;
+
+struct Config {
+  std::vector<std::vector<nghttp2_nv>> nva;
+  std::vector<std::vector<const char *>> nv;
+  nghttp2::Headers custom_headers;
+  std::string scheme;
+  std::string host;
+  std::string ifile;
+  addrinfo *addrs;
+  size_t nreqs;
+  size_t nclients;
+  size_t nthreads;
+  // The maximum number of concurrent streams per session.
+  ssize_t max_concurrent_streams;
+  size_t window_bits;
+  size_t connection_window_bits;
+  enum { PROTO_HTTP2, PROTO_SPDY2, PROTO_SPDY3, PROTO_SPDY3_1 } no_tls_proto;
+  uint16_t port;
+  uint16_t default_port;
+  bool verbose;
+
+  Config();
+  ~Config();
+};
+
+struct RequestStat {
+  RequestStat();
+  // time point when request was sent
+  std::chrono::steady_clock::time_point request_time;
+  // time point when stream was closed
+  std::chrono::steady_clock::time_point stream_close_time;
+  // true if stream was successfully closed.  This means stream was
+  // not reset, but it does not mean HTTP level error (e.g., 404).
+  bool completed;
+};
+
+struct TimeStats {
+  // time for request: max, min, mean and sd (standard deviation)
+  std::chrono::microseconds time_max, time_min, time_mean, time_sd;
+  // percentage of number of requests inside mean -/+ sd
+  double within_sd;
+};
+
+struct Stats {
+  Stats(size_t req_todo);
+  // The total number of requests
+  size_t req_todo;
+  // The number of requests issued so far
+  size_t req_started;
+  // The number of requests finished
+  size_t req_done;
+  // The number of requests completed successfull, but not necessarily
+  // means successful HTTP status code.
+  size_t req_success;
+  // The number of requests marked as success.  HTTP status code is
+  // also considered as success. This is subset of req_done.
+  size_t req_status_success;
+  // The number of requests failed. This is subset of req_done.
+  size_t req_failed;
+  // The number of requests failed due to network errors. This is
+  // subset of req_failed.
+  size_t req_error;
+  // The number of bytes received on the "wire". If SSL/TLS is used,
+  // this is the number of decrypted bytes the application received.
+  int64_t bytes_total;
+  // The number of bytes received in HEADERS frame payload.
+  int64_t bytes_head;
+  // The number of bytes received in DATA frame.
+  int64_t bytes_body;
+  // The number of each HTTP status category, status[i] is status code
+  // in the range [i*100, (i+1)*100).
+  std::array<size_t, 6> status;
+  // The statistics per request
+  std::vector<RequestStat> req_stats;
+};
+
+enum ClientState { CLIENT_IDLE, CLIENT_CONNECTED };
+
+struct Client;
+
+struct Worker {
+  std::vector<std::unique_ptr<Client>> clients;
+  Stats stats;
+  struct ev_loop *loop;
+  SSL_CTX *ssl_ctx;
+  Config *config;
+  size_t progress_interval;
+  uint32_t id;
+  bool tls_info_report_done;
+
+  Worker(uint32_t id, SSL_CTX *ssl_ctx, size_t nreq_todo, size_t nclients,
+         Config *config);
+  ~Worker();
+  Worker(Worker &&o) = default;
+  void run();
+};
+
+struct Stream {
+  int status_success;
+  Stream();
+};
+
+struct Client {
+  std::unordered_map<int32_t, Stream> streams;
+  std::unique_ptr<Session> session;
+  ev_io wev;
+  ev_io rev;
+  std::function<int(Client &)> readfn, writefn;
+  std::function<int(Client &, const uint8_t *, size_t)> on_readfn;
+  std::function<int(Client &)> on_writefn;
+  Worker *worker;
+  SSL *ssl;
+  addrinfo *next_addr;
+  size_t reqidx;
+  ClientState state;
+  // The number of requests this client has to issue.
+  size_t req_todo;
+  // The number of requests this client has issued so far.
+  size_t req_started;
+  // The number of requests this client has done so far.
+  size_t req_done;
+  int fd;
+  Buffer<65536> wb;
+
+  enum { ERR_CONNECT_FAIL = -100 };
+
+  Client(Worker *worker, size_t req_todo);
+  ~Client();
+  int connect();
+  void disconnect();
+  void fail();
+  void submit_request();
+  void process_abandoned_streams();
+  void report_progress();
+  void report_tls_info();
+  void terminate_session();
+
+  int do_read();
+  int do_write();
+
+  int connected();
+  int read_clear();
+  int write_clear();
+  int tls_handshake();
+  int read_tls();
+  int write_tls();
+
+  int on_read(const uint8_t *data, size_t len);
+  int on_write();
+  int on_connect();
+  int noop();
+
+  void on_request(int32_t stream_id);
+  void on_header(int32_t stream_id, const uint8_t *name, size_t namelen,
+                 const uint8_t *value, size_t valuelen);
+  void on_stream_close(int32_t stream_id, bool success, RequestStat *req_stat);
+
+  void record_request_time(RequestStat *req_stat);
+
+  void signal_write();
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_H
diff --git a/src/h2load_http2_session.cc b/src/h2load_http2_session.cc
new file mode 100644 (file)
index 0000000..f5a382b
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load_http2_session.h"
+
+#include <cassert>
+
+#include "h2load.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+Http2Session::Http2Session(Client *client)
+    : client_(client), session_(nullptr) {}
+
+Http2Session::~Http2Session() { nghttp2_session_del(session_); }
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                       const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen, uint8_t flags,
+                       void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
+    return 0;
+  }
+  client->on_header(frame->hd.stream_id, name, namelen, value, valuelen);
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
+    return 0;
+  }
+  client->worker->stats.bytes_head += frame->hd.length;
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  client->worker->stats.bytes_body += len;
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                             uint32_t error_code, void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  auto req_stat = static_cast<RequestStat *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+  client->on_stream_close(stream_id, error_code == NGHTTP2_NO_ERROR, req_stat);
+  return 0;
+}
+} // namespace
+
+namespace {
+int before_frame_send_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, void *user_data) {
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+
+  auto client = static_cast<Client *>(user_data);
+  client->on_request(frame->hd.stream_id);
+  auto req_stat = static_cast<RequestStat *>(
+      nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+  client->record_request_time(req_stat);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
+                      size_t length, int flags, void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  auto &wb = client->wb;
+
+  if (wb.wleft() == 0) {
+    return NGHTTP2_ERR_WOULDBLOCK;
+  }
+
+  return wb.write(data, length);
+}
+} // namespace
+
+void Http2Session::on_connect() {
+  int rv;
+
+  nghttp2_session_callbacks *callbacks;
+
+  nghttp2_session_callbacks_new(&callbacks);
+
+  auto callbacks_deleter = defer(nghttp2_session_callbacks_del, callbacks);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_before_frame_send_callback(
+      callbacks, before_frame_send_callback);
+
+  nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+  nghttp2_session_client_new(&session_, callbacks, client_);
+
+  std::array<nghttp2_settings_entry, 2> iv;
+  iv[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  iv[0].value = 0;
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = (1 << client_->worker->config->window_bits) - 1;
+
+  rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, iv.data(),
+                               iv.size());
+
+  assert(rv == 0);
+
+  auto extra_connection_window =
+      (1 << client_->worker->config->connection_window_bits) - 1 -
+      NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
+  if (extra_connection_window != 0) {
+    nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0,
+                                 extra_connection_window);
+  }
+
+  auto &wb = client_->wb;
+  assert(wb.wleft() >= NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+
+  wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
+           NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+
+  client_->signal_write();
+}
+
+void Http2Session::submit_request(RequestStat *req_stat) {
+  auto config = client_->worker->config;
+  auto &nva = config->nva[client_->reqidx++];
+
+  if (client_->reqidx == config->nva.size()) {
+    client_->reqidx = 0;
+  }
+
+  auto stream_id = nghttp2_submit_request(session_, nullptr, nva.data(),
+                                          nva.size(), nullptr, req_stat);
+  assert(stream_id > 0);
+}
+
+int Http2Session::on_read(const uint8_t *data, size_t len) {
+  auto rv = nghttp2_session_mem_recv(session_, data, len);
+  if (rv < 0) {
+    return -1;
+  }
+
+  assert(static_cast<size_t>(rv) == len);
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
+    return -1;
+  }
+
+  client_->signal_write();
+
+  return 0;
+}
+
+int Http2Session::on_write() {
+  auto rv = nghttp2_session_send(session_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+void Http2Session::terminate() {
+  nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR);
+}
+
+} // namespace h2load
diff --git a/src/h2load_http2_session.h b/src/h2load_http2_session.h
new file mode 100644 (file)
index 0000000..a06e411
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_HTTP2_SESSION_H
+#define H2LOAD_HTTP2_SESSION_H
+
+#include "h2load_session.h"
+
+#include <nghttp2/nghttp2.h>
+
+namespace h2load {
+
+struct Client;
+
+class Http2Session : public Session {
+public:
+  Http2Session(Client *client);
+  virtual ~Http2Session();
+  virtual void on_connect();
+  virtual void submit_request(RequestStat *req_stat);
+  virtual int on_read(const uint8_t *data, size_t len);
+  virtual int on_write();
+  virtual void terminate();
+
+private:
+  Client *client_;
+  nghttp2_session *session_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_HTTP2_SESSION_H
diff --git a/src/h2load_session.h b/src/h2load_session.h
new file mode 100644 (file)
index 0000000..4231b91
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_SESSION_H
+#define H2LOAD_SESSION_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#include "h2load.h"
+
+namespace h2load {
+
+class Session {
+public:
+  virtual ~Session() {}
+  // Called when the connection was made.
+  virtual void on_connect() = 0;
+  // Called when one request must be issued.
+  virtual void submit_request(RequestStat *req_stat) = 0;
+  // Called when incoming bytes are available. The subclass has to
+  // return the number of bytes read.
+  virtual int on_read(const uint8_t *data, size_t len) = 0;
+  // Called when write is available. Returns 0 on success, otherwise
+  // return -1.
+  virtual int on_write() = 0;
+  // Called when the underlying session must be terminated.
+  virtual void terminate() = 0;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_SESSION_H
diff --git a/src/h2load_spdy_session.cc b/src/h2load_spdy_session.cc
new file mode 100644 (file)
index 0000000..25fda42
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "h2load_spdy_session.h"
+
+#include <cassert>
+
+#include "h2load.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace h2load {
+
+SpdySession::SpdySession(Client *client, uint16_t spdy_version)
+    : client_(client), session_(nullptr), spdy_version_(spdy_version) {}
+
+SpdySession::~SpdySession() { spdylay_session_del(session_); }
+
+namespace {
+void before_ctrl_send_callback(spdylay_session *session,
+                               spdylay_frame_type type, spdylay_frame *frame,
+                               void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  if (type != SPDYLAY_SYN_STREAM) {
+    return;
+  }
+  client->on_request(frame->syn_stream.stream_id);
+  auto req_stat =
+      static_cast<RequestStat *>(spdylay_session_get_stream_user_data(
+          session, frame->syn_stream.stream_id));
+  client->record_request_time(req_stat);
+}
+} // namespace
+
+namespace {
+void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
+                           spdylay_frame *frame, void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  if (type != SPDYLAY_SYN_REPLY) {
+    return;
+  }
+  for (auto p = frame->syn_reply.nv; *p; p += 2) {
+    auto name = *p;
+    auto value = *(p + 1);
+    client->on_header(frame->syn_reply.stream_id,
+                      reinterpret_cast<const uint8_t *>(name), strlen(name),
+                      reinterpret_cast<const uint8_t *>(value), strlen(value));
+  }
+  client->worker->stats.bytes_head += frame->syn_reply.hd.length;
+}
+} // namespace
+
+namespace {
+void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
+                                 int32_t stream_id, const uint8_t *data,
+                                 size_t len, void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  client->worker->stats.bytes_body += len;
+
+  auto spdy_session = static_cast<SpdySession *>(client->session.get());
+
+  spdy_session->handle_window_update(stream_id, len);
+}
+} // namespace
+
+namespace {
+void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
+                              spdylay_status_code status_code,
+                              void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  auto req_stat = static_cast<RequestStat *>(
+      spdylay_session_get_stream_user_data(session, stream_id));
+  client->on_stream_close(stream_id, status_code == SPDYLAY_OK, req_stat);
+}
+} // namespace
+
+namespace {
+ssize_t send_callback(spdylay_session *session, const uint8_t *data,
+                      size_t length, int flags, void *user_data) {
+  auto client = static_cast<Client *>(user_data);
+  auto &wb = client->wb;
+
+  if (wb.wleft() == 0) {
+    return SPDYLAY_ERR_DEFERRED;
+  }
+
+  return wb.write(data, length);
+}
+} // namespace
+
+void SpdySession::on_connect() {
+  spdylay_session_callbacks callbacks = {0};
+  callbacks.send_callback = send_callback;
+  callbacks.before_ctrl_send_callback = before_ctrl_send_callback;
+  callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+  callbacks.on_stream_close_callback = on_stream_close_callback;
+  callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback;
+
+  spdylay_session_client_new(&session_, spdy_version_, &callbacks, client_);
+
+  int val = 1;
+  spdylay_session_set_option(session_, SPDYLAY_OPT_NO_AUTO_WINDOW_UPDATE, &val,
+                             sizeof(val));
+
+  spdylay_settings_entry iv;
+  iv.settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv.flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
+  iv.value = (1 << client_->worker->config->window_bits);
+  spdylay_submit_settings(session_, SPDYLAY_FLAG_SETTINGS_NONE, &iv, 1);
+
+  auto config = client_->worker->config;
+
+  if (spdy_version_ >= SPDYLAY_PROTO_SPDY3_1 &&
+      config->connection_window_bits > 16) {
+    auto delta =
+        (1 << config->connection_window_bits) - SPDYLAY_INITIAL_WINDOW_SIZE;
+    spdylay_submit_window_update(session_, 0, delta);
+  }
+
+  client_->signal_write();
+}
+
+void SpdySession::submit_request(RequestStat *req_stat) {
+  auto config = client_->worker->config;
+  auto &nv = config->nv[client_->reqidx++];
+
+  if (client_->reqidx == config->nv.size()) {
+    client_->reqidx = 0;
+  }
+
+  spdylay_submit_request(session_, 0, nv.data(), nullptr, req_stat);
+}
+
+int SpdySession::on_read(const uint8_t *data, size_t len) {
+  auto rv = spdylay_session_mem_recv(session_, data, len);
+  if (rv < 0) {
+    return -1;
+  }
+
+  assert(static_cast<size_t>(rv) == len);
+
+  if (spdylay_session_want_read(session_) == 0 &&
+      spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
+    return -1;
+  }
+
+  client_->signal_write();
+
+  return 0;
+}
+
+int SpdySession::on_write() {
+  auto rv = spdylay_session_send(session_);
+  if (rv != 0) {
+    return -1;
+  }
+
+  if (spdylay_session_want_read(session_) == 0 &&
+      spdylay_session_want_write(session_) == 0 && client_->wb.rleft() == 0) {
+    return -1;
+  }
+  return 0;
+}
+
+void SpdySession::terminate() {
+  spdylay_session_fail_session(session_, SPDYLAY_OK);
+}
+
+namespace {
+int32_t determine_window_update_transmission(spdylay_session *session,
+                                             int32_t stream_id,
+                                             size_t window_bits) {
+  int32_t recv_length;
+
+  if (stream_id == 0) {
+    recv_length = spdylay_session_get_recv_data_length(session);
+  } else {
+    recv_length =
+        spdylay_session_get_stream_recv_data_length(session, stream_id);
+  }
+
+  auto window_size = 1 << window_bits;
+
+  if (recv_length != -1 && recv_length >= window_size / 2) {
+    return recv_length;
+  }
+
+  return -1;
+}
+} // namespace
+
+void SpdySession::handle_window_update(int32_t stream_id, size_t recvlen) {
+  auto config = client_->worker->config;
+  size_t connection_window_bits;
+
+  if (config->connection_window_bits > 16) {
+    connection_window_bits = config->connection_window_bits;
+  } else {
+    connection_window_bits = 16;
+  }
+
+  auto delta =
+      determine_window_update_transmission(session_, 0, connection_window_bits);
+  if (delta > 0) {
+    spdylay_submit_window_update(session_, 0, delta);
+  }
+
+  delta = determine_window_update_transmission(session_, stream_id,
+                                               config->window_bits);
+  if (delta > 0) {
+    spdylay_submit_window_update(session_, stream_id, delta);
+  }
+}
+
+} // namespace h2load
diff --git a/src/h2load_spdy_session.h b/src/h2load_spdy_session.h
new file mode 100644 (file)
index 0000000..f76f25a
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef H2LOAD_SPDY_SESSION_H
+#define H2LOAD_SPDY_SESSION_H
+
+#include "h2load_session.h"
+
+#include <spdylay/spdylay.h>
+
+#include "util.h"
+
+namespace h2load {
+
+struct Client;
+
+class SpdySession : public Session {
+public:
+  SpdySession(Client *client, uint16_t spdy_version);
+  virtual ~SpdySession();
+  virtual void on_connect();
+  virtual void submit_request(RequestStat *req_stat);
+  virtual int on_read(const uint8_t *data, size_t len);
+  virtual int on_write();
+  virtual void terminate();
+  void handle_window_update(int32_t stream_id, size_t recvlen);
+
+private:
+  Client *client_;
+  spdylay_session *session_;
+  uint16_t spdy_version_;
+};
+
+} // namespace h2load
+
+#endif // H2LOAD_SPDY_SESSION_H
diff --git a/src/http-parser.patch b/src/http-parser.patch
new file mode 100644 (file)
index 0000000..ef80940
--- /dev/null
@@ -0,0 +1,28 @@
+commit a143133d43420ef89e4ba0d84c73998863cf9f81
+Author: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com>
+Date:   Wed Jul 11 18:46:00 2012 +0900
+
+    Use http_parser for tunneling connection transparently
+
+diff --git a/examples/http-parser/http_parser.c b/examples/http-parser/http_parser.c
+index 0c11eb8..610da57 100644
+--- a/examples/http-parser/http_parser.c
++++ b/examples/http-parser/http_parser.c
+@@ -1627,9 +1627,14 @@ size_t http_parser_execute (http_parser *parser,
+         /* Exit, the rest of the connect is in a different protocol. */
+         if (parser->upgrade) {
+-          parser->state = NEW_MESSAGE();
+-          CALLBACK_NOTIFY(message_complete);
+-          return (p - data) + 1;
++          /* We want to use http_parser for tunneling connection
++             transparently */
++          /* Read body until EOF */
++          parser->state = s_body_identity_eof;
++          break;
++          /* parser->state = NEW_MESSAGE(); */
++          /* CALLBACK_NOTIFY(message_complete); */
++          /* return (p - data) + 1; */
+         }
+         if (parser->flags & F_SKIPBODY) {
diff --git a/src/http2.cc b/src/http2.cc
new file mode 100644 (file)
index 0000000..d580481
--- /dev/null
@@ -0,0 +1,1009 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "http2.h"
+
+#include "util.h"
+
+namespace nghttp2 {
+
+namespace http2 {
+
+std::string get_status_string(unsigned int status_code) {
+  switch (status_code) {
+  case 100:
+    return "100 Continue";
+  case 101:
+    return "101 Switching Protocols";
+  case 200:
+    return "200 OK";
+  case 201:
+    return "201 Created";
+  case 202:
+    return "202 Accepted";
+  case 203:
+    return "203 Non-Authoritative Information";
+  case 204:
+    return "204 No Content";
+  case 205:
+    return "205 Reset Content";
+  case 206:
+    return "206 Partial Content";
+  case 300:
+    return "300 Multiple Choices";
+  case 301:
+    return "301 Moved Permanently";
+  case 302:
+    return "302 Found";
+  case 303:
+    return "303 See Other";
+  case 304:
+    return "304 Not Modified";
+  case 305:
+    return "305 Use Proxy";
+  // case 306: return "306 (Unused)";
+  case 307:
+    return "307 Temporary Redirect";
+  case 308:
+    return "308 Permanent Redirect";
+  case 400:
+    return "400 Bad Request";
+  case 401:
+    return "401 Unauthorized";
+  case 402:
+    return "402 Payment Required";
+  case 403:
+    return "403 Forbidden";
+  case 404:
+    return "404 Not Found";
+  case 405:
+    return "405 Method Not Allowed";
+  case 406:
+    return "406 Not Acceptable";
+  case 407:
+    return "407 Proxy Authentication Required";
+  case 408:
+    return "408 Request Timeout";
+  case 409:
+    return "409 Conflict";
+  case 410:
+    return "410 Gone";
+  case 411:
+    return "411 Length Required";
+  case 412:
+    return "412 Precondition Failed";
+  case 413:
+    return "413 Payload Too Large";
+  case 414:
+    return "414 URI Too Long";
+  case 415:
+    return "415 Unsupported Media Type";
+  case 416:
+    return "416 Requested Range Not Satisfiable";
+  case 417:
+    return "417 Expectation Failed";
+  case 421:
+    return "421 Misdirected Request";
+  case 426:
+    return "426 Upgrade Required";
+  case 428:
+    return "428 Precondition Required";
+  case 429:
+    return "429 Too Many Requests";
+  case 431:
+    return "431 Request Header Fields Too Large";
+  case 500:
+    return "500 Internal Server Error";
+  case 501:
+    return "501 Not Implemented";
+  case 502:
+    return "502 Bad Gateway";
+  case 503:
+    return "503 Service Unavailable";
+  case 504:
+    return "504 Gateway Timeout";
+  case 505:
+    return "505 HTTP Version Not Supported";
+  case 511:
+    return "511 Network Authentication Required";
+  default:
+    return util::utos(status_code);
+  }
+}
+
+void capitalize(std::string &s, size_t offset) {
+  s[offset] = util::upcase(s[offset]);
+  for (size_t i = offset + 1, eoi = s.size(); i < eoi; ++i) {
+    if (s[i - 1] == '-') {
+      s[i] = util::upcase(s[i]);
+    } else {
+      s[i] = util::lowcase(s[i]);
+    }
+  }
+}
+
+bool lws(const char *value) {
+  for (; *value; ++value) {
+    switch (*value) {
+    case '\t':
+    case ' ':
+      continue;
+    default:
+      return false;
+    }
+  }
+  return true;
+}
+
+void copy_url_component(std::string &dest, const http_parser_url *u, int field,
+                        const char *url) {
+  if (u->field_set & (1 << field)) {
+    dest.assign(url + u->field_data[field].off, u->field_data[field].len);
+  }
+}
+
+Headers::value_type to_header(const uint8_t *name, size_t namelen,
+                              const uint8_t *value, size_t valuelen,
+                              bool no_index, int16_t token) {
+  return Header(std::string(reinterpret_cast<const char *>(name), namelen),
+                std::string(reinterpret_cast<const char *>(value), valuelen),
+                no_index, token);
+}
+
+void add_header(Headers &nva, const uint8_t *name, size_t namelen,
+                const uint8_t *value, size_t valuelen, bool no_index,
+                int16_t token) {
+  if (valuelen > 0) {
+    size_t i, j;
+    for (i = 0; i < valuelen && (value[i] == ' ' || value[i] == '\t'); ++i)
+      ;
+    for (j = valuelen - 1; j > i && (value[j] == ' ' || value[j] == '\t'); --j)
+      ;
+    value += i;
+    valuelen -= i + (valuelen - j - 1);
+  }
+  nva.push_back(to_header(name, namelen, value, valuelen, no_index, token));
+}
+
+const Headers::value_type *get_header(const Headers &nva, const char *name) {
+  const Headers::value_type *res = nullptr;
+  for (auto &nv : nva) {
+    if (nv.name == name) {
+      res = &nv;
+    }
+  }
+  return res;
+}
+
+std::string value_to_str(const Headers::value_type *nv) {
+  if (nv) {
+    return nv->value;
+  }
+  return "";
+}
+
+bool non_empty_value(const Headers::value_type *nv) {
+  return nv && !nv->value.empty();
+}
+
+nghttp2_nv make_nv(const std::string &name, const std::string &value,
+                   bool no_index) {
+  uint8_t flags;
+
+  flags = no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE;
+
+  return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
+          value.size(), flags};
+}
+
+void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
+  for (auto &kv : headers) {
+    if (kv.name.empty() || kv.name[0] == ':') {
+      continue;
+    }
+    switch (kv.token) {
+    case HD_COOKIE:
+    case HD_CONNECTION:
+    case HD_HOST:
+    case HD_HTTP2_SETTINGS:
+    case HD_KEEP_ALIVE:
+    case HD_PROXY_CONNECTION:
+    case HD_SERVER:
+    case HD_TRAILER:
+    case HD_TRANSFER_ENCODING:
+    case HD_UPGRADE:
+    case HD_VIA:
+    case HD_X_FORWARDED_FOR:
+    case HD_X_FORWARDED_PROTO:
+      continue;
+    }
+    nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
+  }
+}
+
+void build_http1_headers_from_headers(std::string &hdrs,
+                                      const Headers &headers) {
+  for (auto &kv : headers) {
+    if (kv.name.empty() || kv.name[0] == ':') {
+      continue;
+    }
+    switch (kv.token) {
+    case HD_CONNECTION:
+    case HD_COOKIE:
+    case HD_HOST:
+    case HD_HTTP2_SETTINGS:
+    case HD_KEEP_ALIVE:
+    case HD_PROXY_CONNECTION:
+    case HD_SERVER:
+    case HD_TRAILER:
+    case HD_UPGRADE:
+    case HD_VIA:
+    case HD_X_FORWARDED_FOR:
+    case HD_X_FORWARDED_PROTO:
+      continue;
+    }
+    hdrs += kv.name;
+    capitalize(hdrs, hdrs.size() - kv.name.size());
+    hdrs += ": ";
+    hdrs += kv.value;
+    hdrs += "\r\n";
+  }
+}
+
+int32_t determine_window_update_transmission(nghttp2_session *session,
+                                             int32_t stream_id) {
+  int32_t recv_length, window_size;
+  if (stream_id == 0) {
+    recv_length = nghttp2_session_get_effective_recv_data_length(session);
+    window_size = nghttp2_session_get_effective_local_window_size(session);
+  } else {
+    recv_length = nghttp2_session_get_stream_effective_recv_data_length(
+        session, stream_id);
+    window_size = nghttp2_session_get_stream_effective_local_window_size(
+        session, stream_id);
+  }
+  if (recv_length != -1 && window_size != -1) {
+    if (recv_length >= window_size / 2) {
+      return recv_length;
+    }
+  }
+  return -1;
+}
+
+void dump_nv(FILE *out, const char **nv) {
+  for (size_t i = 0; nv[i]; i += 2) {
+    fwrite(nv[i], strlen(nv[i]), 1, out);
+    fwrite(": ", 2, 1, out);
+    fwrite(nv[i + 1], strlen(nv[i + 1]), 1, out);
+    fwrite("\n", 1, 1, out);
+  }
+  fwrite("\n", 1, 1, out);
+  fflush(out);
+}
+
+void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen) {
+  auto end = nva + nvlen;
+  for (; nva != end; ++nva) {
+    fwrite(nva->name, nva->namelen, 1, out);
+    fwrite(": ", 2, 1, out);
+    fwrite(nva->value, nva->valuelen, 1, out);
+    fwrite("\n", 1, 1, out);
+  }
+  fwrite("\n", 1, 1, out);
+  fflush(out);
+}
+
+void dump_nv(FILE *out, const Headers &nva) {
+  for (auto &nv : nva) {
+    fwrite(nv.name.c_str(), nv.name.size(), 1, out);
+    fwrite(": ", 2, 1, out);
+    fwrite(nv.value.c_str(), nv.value.size(), 1, out);
+    fwrite("\n", 1, 1, out);
+  }
+  fwrite("\n", 1, 1, out);
+  fflush(out);
+}
+
+std::string rewrite_location_uri(const std::string &uri,
+                                 const http_parser_url &u,
+                                 const std::string &match_host,
+                                 const std::string &request_authority,
+                                 const std::string &upstream_scheme) {
+  // We just rewrite scheme and authority.
+  if ((u.field_set & (1 << UF_HOST)) == 0) {
+    return "";
+  }
+  auto field = &u.field_data[UF_HOST];
+  if (!util::startsWith(std::begin(match_host), std::end(match_host),
+                        &uri[field->off], &uri[field->off] + field->len) ||
+      (match_host.size() != field->len && match_host[field->len] != ':')) {
+    return "";
+  }
+  std::string res;
+  if (!request_authority.empty()) {
+    res += upstream_scheme;
+    res += "://";
+    res += request_authority;
+  }
+  if (u.field_set & (1 << UF_PATH)) {
+    field = &u.field_data[UF_PATH];
+    res.append(&uri[field->off], field->len);
+  }
+  if (u.field_set & (1 << UF_QUERY)) {
+    field = &u.field_data[UF_QUERY];
+    res += "?";
+    res.append(&uri[field->off], field->len);
+  }
+  if (u.field_set & (1 << UF_FRAGMENT)) {
+    field = &u.field_data[UF_FRAGMENT];
+    res += "#";
+    res.append(&uri[field->off], field->len);
+  }
+  return res;
+}
+
+int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
+             size_t valuelen) {
+  if (!nghttp2_check_header_name(name, namelen)) {
+    return 0;
+  }
+  if (!nghttp2_check_header_value(value, valuelen)) {
+    return 0;
+  }
+  return 1;
+}
+
+int parse_http_status_code(const std::string &src) {
+  if (src.size() != 3) {
+    return -1;
+  }
+
+  int status = 0;
+  for (auto c : src) {
+    if (!isdigit(c)) {
+      return -1;
+    }
+    status *= 10;
+    status += c - '0';
+  }
+
+  if (status < 100) {
+    return -1;
+  }
+
+  return status;
+}
+
+int lookup_token(const std::string &name) {
+  return lookup_token(reinterpret_cast<const uint8_t *>(name.c_str()),
+                      name.size());
+}
+
+// This function was generated by genheaderfunc.py.  Inspired by h2o
+// header lookup.  https://github.com/h2o/h2o
+int lookup_token(const uint8_t *name, size_t namelen) {
+  switch (namelen) {
+  case 2:
+    switch (name[namelen - 1]) {
+    case 'e':
+      if (util::streq("t", name, 1)) {
+        return HD_TE;
+      }
+      break;
+    }
+    break;
+  case 3:
+    switch (name[namelen - 1]) {
+    case 'a':
+      if (util::streq("vi", name, 2)) {
+        return HD_VIA;
+      }
+      break;
+    }
+    break;
+  case 4:
+    switch (name[namelen - 1]) {
+    case 'k':
+      if (util::streq("lin", name, 3)) {
+        return HD_LINK;
+      }
+      break;
+    case 't':
+      if (util::streq("hos", name, 3)) {
+        return HD_HOST;
+      }
+      break;
+    }
+    break;
+  case 5:
+    switch (name[namelen - 1]) {
+    case 'h':
+      if (util::streq(":pat", name, 4)) {
+        return HD__PATH;
+      }
+      break;
+    case 't':
+      if (util::streq(":hos", name, 4)) {
+        return HD__HOST;
+      }
+      break;
+    }
+    break;
+  case 6:
+    switch (name[namelen - 1]) {
+    case 'e':
+      if (util::streq("cooki", name, 5)) {
+        return HD_COOKIE;
+      }
+      break;
+    case 'r':
+      if (util::streq("serve", name, 5)) {
+        return HD_SERVER;
+      }
+      break;
+    case 't':
+      if (util::streq("expec", name, 5)) {
+        return HD_EXPECT;
+      }
+      break;
+    }
+    break;
+  case 7:
+    switch (name[namelen - 1]) {
+    case 'c':
+      if (util::streq("alt-sv", name, 6)) {
+        return HD_ALT_SVC;
+      }
+      break;
+    case 'd':
+      if (util::streq(":metho", name, 6)) {
+        return HD__METHOD;
+      }
+      break;
+    case 'e':
+      if (util::streq(":schem", name, 6)) {
+        return HD__SCHEME;
+      }
+      if (util::streq("upgrad", name, 6)) {
+        return HD_UPGRADE;
+      }
+      break;
+    case 'r':
+      if (util::streq("traile", name, 6)) {
+        return HD_TRAILER;
+      }
+      break;
+    case 's':
+      if (util::streq(":statu", name, 6)) {
+        return HD__STATUS;
+      }
+      break;
+    }
+    break;
+  case 8:
+    switch (name[namelen - 1]) {
+    case 'n':
+      if (util::streq("locatio", name, 7)) {
+        return HD_LOCATION;
+      }
+      break;
+    }
+    break;
+  case 10:
+    switch (name[namelen - 1]) {
+    case 'e':
+      if (util::streq("keep-aliv", name, 9)) {
+        return HD_KEEP_ALIVE;
+      }
+      break;
+    case 'n':
+      if (util::streq("connectio", name, 9)) {
+        return HD_CONNECTION;
+      }
+      break;
+    case 't':
+      if (util::streq("user-agen", name, 9)) {
+        return HD_USER_AGENT;
+      }
+      break;
+    case 'y':
+      if (util::streq(":authorit", name, 9)) {
+        return HD__AUTHORITY;
+      }
+      break;
+    }
+    break;
+  case 13:
+    switch (name[namelen - 1]) {
+    case 'l':
+      if (util::streq("cache-contro", name, 12)) {
+        return HD_CACHE_CONTROL;
+      }
+      break;
+    }
+    break;
+  case 14:
+    switch (name[namelen - 1]) {
+    case 'h':
+      if (util::streq("content-lengt", name, 13)) {
+        return HD_CONTENT_LENGTH;
+      }
+      break;
+    case 's':
+      if (util::streq("http2-setting", name, 13)) {
+        return HD_HTTP2_SETTINGS;
+      }
+      break;
+    }
+    break;
+  case 15:
+    switch (name[namelen - 1]) {
+    case 'e':
+      if (util::streq("accept-languag", name, 14)) {
+        return HD_ACCEPT_LANGUAGE;
+      }
+      break;
+    case 'g':
+      if (util::streq("accept-encodin", name, 14)) {
+        return HD_ACCEPT_ENCODING;
+      }
+      break;
+    case 'r':
+      if (util::streq("x-forwarded-fo", name, 14)) {
+        return HD_X_FORWARDED_FOR;
+      }
+      break;
+    }
+    break;
+  case 16:
+    switch (name[namelen - 1]) {
+    case 'n':
+      if (util::streq("proxy-connectio", name, 15)) {
+        return HD_PROXY_CONNECTION;
+      }
+      break;
+    }
+    break;
+  case 17:
+    switch (name[namelen - 1]) {
+    case 'e':
+      if (util::streq("if-modified-sinc", name, 16)) {
+        return HD_IF_MODIFIED_SINCE;
+      }
+      break;
+    case 'g':
+      if (util::streq("transfer-encodin", name, 16)) {
+        return HD_TRANSFER_ENCODING;
+      }
+      break;
+    case 'o':
+      if (util::streq("x-forwarded-prot", name, 16)) {
+        return HD_X_FORWARDED_PROTO;
+      }
+      break;
+    }
+    break;
+  }
+  return -1;
+}
+
+void init_hdidx(HeaderIndex &hdidx) {
+  std::fill(std::begin(hdidx), std::end(hdidx), -1);
+}
+
+void index_header(HeaderIndex &hdidx, int16_t token, size_t idx) {
+  if (token == -1) {
+    return;
+  }
+  assert(token < HD_MAXIDX);
+  hdidx[token] = idx;
+}
+
+bool check_http2_request_pseudo_header(const HeaderIndex &hdidx,
+                                       int16_t token) {
+  switch (token) {
+  case HD__AUTHORITY:
+  case HD__METHOD:
+  case HD__PATH:
+  case HD__SCHEME:
+    return hdidx[token] == -1;
+  default:
+    return false;
+  }
+}
+
+bool check_http2_response_pseudo_header(const HeaderIndex &hdidx,
+                                        int16_t token) {
+  switch (token) {
+  case HD__STATUS:
+    return hdidx[token] == -1;
+  default:
+    return false;
+  }
+}
+
+bool http2_header_allowed(int16_t token) {
+  switch (token) {
+  case HD_CONNECTION:
+  case HD_KEEP_ALIVE:
+  case HD_PROXY_CONNECTION:
+  case HD_TRANSFER_ENCODING:
+  case HD_UPGRADE:
+    return false;
+  default:
+    return true;
+  }
+}
+
+bool http2_mandatory_request_headers_presence(const HeaderIndex &hdidx) {
+  if (hdidx[HD__METHOD] == -1 || hdidx[HD__PATH] == -1 ||
+      hdidx[HD__SCHEME] == -1 ||
+      (hdidx[HD__AUTHORITY] == -1 && hdidx[HD_HOST] == -1)) {
+    return false;
+  }
+  return true;
+}
+
+const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
+                                      const Headers &nva) {
+  auto i = hdidx[token];
+  if (i == -1) {
+    return nullptr;
+  }
+  return &nva[i];
+}
+
+namespace {
+template <typename InputIt> InputIt skip_lws(InputIt first, InputIt last) {
+  for (; first != last; ++first) {
+    switch (*first) {
+    case ' ':
+    case '\t':
+      continue;
+    default:
+      return first;
+    }
+  }
+  return first;
+}
+} // namespace
+
+namespace {
+template <typename InputIt>
+InputIt skip_to_next_field(InputIt first, InputIt last) {
+  for (; first != last; ++first) {
+    switch (*first) {
+    case ' ':
+    case '\t':
+    case ',':
+      continue;
+    default:
+      return first;
+    }
+  }
+  return first;
+}
+} // namespace
+
+namespace {
+std::pair<LinkHeader, const char *>
+parse_next_link_header_once(const char *first, const char *last) {
+  first = skip_to_next_field(first, last);
+  if (first == last || *first != '<') {
+    return {{{0, 0}}, last};
+  }
+  auto url_first = ++first;
+  first = std::find(first, last, '>');
+  if (first == last) {
+    return {{{0, 0}}, first};
+  }
+  auto url_last = first++;
+  if (first == last) {
+    return {{{0, 0}}, first};
+  }
+  // we expect ';' or ',' here
+  switch (*first) {
+  case ',':
+    return {{{0, 0}}, ++first};
+  case ';':
+    ++first;
+    break;
+  default:
+    return {{{0, 0}}, last};
+  }
+
+  auto ok = false;
+  for (;;) {
+    first = skip_lws(first, last);
+    if (first == last) {
+      return {{{0, 0}}, first};
+    }
+    // we expect link-param
+
+    // we are only interested in rel=preload parameter.  Others are
+    // simply skipped.
+    static const char PL[] = "rel=preload";
+    static const size_t PLLEN = sizeof(PL) - 1;
+    if (first + PLLEN == last) {
+      if (std::equal(PL, PL + PLLEN, first)) {
+        ok = true;
+        // this is the end of sequence
+        return {{{url_first, url_last}}, last};
+      }
+    } else if (first + PLLEN + 1 <= last) {
+      switch (*(first + PLLEN)) {
+      case ',':
+        if (!std::equal(PL, PL + PLLEN, first)) {
+          break;
+        }
+        ok = true;
+        // skip including ','
+        first += PLLEN + 1;
+        return {{{url_first, url_last}}, first};
+      case ';':
+        if (!std::equal(PL, PL + PLLEN, first)) {
+          break;
+        }
+        ok = true;
+        // skip including ';'
+        first += PLLEN + 1;
+        // continue parse next link-param
+        continue;
+      }
+    }
+    auto param_first = first;
+    for (; first != last;) {
+      if (util::in_attr_char(*first)) {
+        ++first;
+        continue;
+      }
+      // '*' is only allowed at the end of parameter name and must be
+      // followed by '='
+      if (last - first >= 2 && first != param_first) {
+        if (*first == '*' && *(first + 1) == '=') {
+          ++first;
+          break;
+        }
+      }
+      if (*first == '=' || *first == ';' || *first == ',') {
+        break;
+      }
+      return {{{0, 0}}, last};
+    }
+    if (param_first == first) {
+      // empty parmname
+      return {{{0, 0}}, last};
+    }
+    // link-param without value is acceptable (see link-extension) if
+    // it is not followed by '='
+    if (first == last || *first == ',') {
+      goto almost_done;
+    }
+    if (*first == ';') {
+      ++first;
+      // parse next link-param
+      continue;
+    }
+    // now parsing lin-param value
+    assert(*first == '=');
+    ++first;
+    if (first == last) {
+      // empty value is not acceptable
+      return {{{0, 0}}, first};
+    }
+    if (*first == '"') {
+      // quoted-string
+      first = std::find(first + 1, last, '"');
+      if (first == last) {
+        return {{{0, 0}}, first};
+      }
+      ++first;
+      if (first == last || *first == ',') {
+        goto almost_done;
+      }
+      if (*first == ';') {
+        ++first;
+        // parse next link-param
+        continue;
+      }
+      return {{{0, 0}}, last};
+    }
+    // not quoted-string, skip to next ',' or ';'
+    if (*first == ',' || *first == ';') {
+      // empty value
+      return {{{0, 0}}, last};
+    }
+    for (; first != last; ++first) {
+      if (*first == ',' || *first == ';') {
+        break;
+      }
+    }
+    if (first == last || *first == ',') {
+      goto almost_done;
+    }
+    assert(*first == ';');
+    ++first;
+    // parse next link-param
+  }
+
+almost_done:
+  assert(first == last || *first == ',');
+
+  if (*first == ',') {
+    ++first;
+  }
+  if (ok) {
+    return {{{url_first, url_last}}, first};
+  }
+  return {{{0, 0}}, first};
+}
+} // namespace
+
+std::vector<LinkHeader> parse_link_header(const char *src, size_t len) {
+  auto first = src;
+  auto last = src + len;
+  std::vector<LinkHeader> res;
+  for (; first != last;) {
+    auto rv = parse_next_link_header_once(first, last);
+    first = rv.second;
+    if (rv.first.uri.first != 0 || rv.first.uri.second != 0) {
+      res.push_back(rv.first);
+    }
+  }
+  return res;
+}
+
+namespace {
+void eat_file(std::string &path) {
+  if (path.empty()) {
+    path = "/";
+    return;
+  }
+  auto p = path.size() - 1;
+  if (path[p] == '/') {
+    return;
+  }
+  p = path.rfind('/', p);
+  if (p == std::string::npos) {
+    // this should not happend in normal case, where we expect path
+    // starts with '/'
+    path = "/";
+    return;
+  }
+  path.erase(std::begin(path) + p + 1, std::end(path));
+}
+} // namespace
+
+namespace {
+void eat_dir(std::string &path) {
+  if (path.empty()) {
+    path = "/";
+    return;
+  }
+  auto p = path.size() - 1;
+  if (path[p] != '/') {
+    p = path.rfind('/', p);
+    if (p == std::string::npos) {
+      // this should not happend in normal case, where we expect path
+      // starts with '/'
+      path = "/";
+      return;
+    }
+  }
+  if (path[p] == '/') {
+    if (p == 0) {
+      return;
+    }
+    --p;
+  }
+  p = path.rfind('/', p);
+  if (p == std::string::npos) {
+    // this should not happend in normal case, where we expect path
+    // starts with '/'
+    path = "/";
+    return;
+  }
+  path.erase(std::begin(path) + p + 1, std::end(path));
+}
+} // namespace
+
+std::string path_join(const char *base_path, size_t base_pathlen,
+                      const char *base_query, size_t base_querylen,
+                      const char *rel_path, size_t rel_pathlen,
+                      const char *rel_query, size_t rel_querylen) {
+  std::string res;
+  if (rel_pathlen == 0) {
+    if (base_pathlen == 0) {
+      res = "/";
+    } else {
+      res.assign(base_path, base_pathlen);
+    }
+    if (rel_querylen == 0) {
+      if (base_querylen) {
+        res += "?";
+        res.append(base_query, base_querylen);
+      }
+      return res;
+    }
+    res += "?";
+    res.append(rel_query, rel_querylen);
+    return res;
+  }
+
+  auto first = rel_path;
+  auto last = rel_path + rel_pathlen;
+
+  if (rel_path[0] == '/') {
+    res = "/";
+    ++first;
+  } else if (base_pathlen == 0) {
+    res = "/";
+  } else {
+    res.assign(base_path, base_pathlen);
+  }
+
+  for (; first != last;) {
+    if (*first == '.') {
+      if (first + 1 == last) {
+        break;
+      }
+      if (*(first + 1) == '/') {
+        first += 2;
+        continue;
+      }
+      if (*(first + 1) == '.') {
+        if (first + 2 == last) {
+          eat_dir(res);
+          break;
+        }
+        if (*(first + 2) == '/') {
+          eat_dir(res);
+          first += 3;
+          continue;
+        }
+      }
+    }
+    if (res.back() != '/') {
+      eat_file(res);
+    }
+    auto slash = std::find(first, last, '/');
+    if (slash == last) {
+      res.append(first, last);
+      break;
+    }
+    res.append(first, slash + 1);
+    first = slash + 1;
+    for (; first != last && *first == '/'; ++first)
+      ;
+  }
+  if (rel_querylen) {
+    res += "?";
+    res.append(rel_query, rel_querylen);
+  }
+  return res;
+}
+
+} // namespace http2
+
+} // namespace nghttp2
diff --git a/src/http2.h b/src/http2.h
new file mode 100644 (file)
index 0000000..5e9b356
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef HTTP2_H
+#define HTTP2_H
+
+#include "nghttp2_config.h"
+
+#include <cstdio>
+#include <cstring>
+#include <string>
+#include <vector>
+#include <array>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http-parser/http_parser.h"
+
+namespace nghttp2 {
+
+struct Header {
+  Header(std::string name, std::string value, bool no_index = false,
+         int16_t token = -1)
+      : name(std::move(name)), value(std::move(value)), token(token),
+        no_index(no_index) {}
+
+  Header() : token(-1), no_index(false) {}
+
+  bool operator==(const Header &other) const {
+    return name == other.name && value == other.value;
+  }
+
+  bool operator<(const Header &rhs) const {
+    return name < rhs.name || (name == rhs.name && value < rhs.value);
+  }
+
+  std::string name;
+  std::string value;
+  int16_t token;
+  bool no_index;
+};
+
+typedef std::vector<Header> Headers;
+
+namespace http2 {
+
+std::string get_status_string(unsigned int status_code);
+
+void capitalize(std::string &s, size_t offset);
+
+// Returns true if |value| is LWS
+bool lws(const char *value);
+
+// Copies the |field| component value from |u| and |url| to the
+// |dest|. If |u| does not have |field|, then this function does
+// nothing.
+void copy_url_component(std::string &dest, const http_parser_url *u, int field,
+                        const char *url);
+
+Headers::value_type to_header(const uint8_t *name, size_t namelen,
+                              const uint8_t *value, size_t valuelen,
+                              bool no_index, int16_t token);
+
+// Add name/value pairs to |nva|.  If |no_index| is true, this
+// name/value pair won't be indexed when it is forwarded to the next
+// hop.  This function strips white spaces around |value|.
+void add_header(Headers &nva, const uint8_t *name, size_t namelen,
+                const uint8_t *value, size_t valuelen, bool no_index,
+                int16_t token);
+
+// Returns pointer to the entry in |nva| which has name |name|.  If
+// more than one entries which have the name |name|, last occurrence
+// in |nva| is returned.  If no such entry exist, returns nullptr.
+const Headers::value_type *get_header(const Headers &nva, const char *name);
+
+// Returns nv->second if nv is not nullptr. Otherwise, returns "".
+std::string value_to_str(const Headers::value_type *nv);
+
+// Returns true if the value of |nv| is not empty.
+bool non_empty_value(const Headers::value_type *nv);
+
+// Creates nghttp2_nv using |name| and |value| and returns it. The
+// returned value only references the data pointer to name.c_str() and
+// value.c_str().  If |no_index| is true, nghttp2_nv flags member has
+// NGHTTP2_NV_FLAG_NO_INDEX flag set.
+nghttp2_nv make_nv(const std::string &name, const std::string &value,
+                   bool no_index = false);
+
+// Create nghttp2_nv from string literal |name| and |value|.
+template <size_t N, size_t M>
+nghttp2_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
+  return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
+          NGHTTP2_NV_FLAG_NONE};
+}
+
+// Create nghttp2_nv from string literal |name| and c-string |value|.
+template <size_t N>
+nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) {
+  return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
+          NGHTTP2_NV_FLAG_NONE};
+}
+
+// Create nghttp2_nv from string literal |name| and std::string
+// |value|.
+template <size_t N>
+nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
+  return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
+          NGHTTP2_NV_FLAG_NONE};
+}
+
+// Appends headers in |headers| to |nv|.  |headers| must be indexed
+// before this call (its element's token field is assigned).  Certain
+// headers, including disallowed headers in HTTP/2 spec and headers
+// which require special handling (i.e. via), are not copied.
+void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
+
+// Appends HTTP/1.1 style header lines to |hdrs| from headers in
+// |headers|.  |headers| must be indexed before this call (its
+// element's token field is assigned).  Certain headers, which
+// requires special handling (i.e. via and cookie), are not appended.
+void build_http1_headers_from_headers(std::string &hdrs,
+                                      const Headers &headers);
+
+// Return positive window_size_increment if WINDOW_UPDATE should be
+// sent for the stream |stream_id|. If |stream_id| == 0, this function
+// determines the necessity of the WINDOW_UPDATE for a connection.
+//
+// If the function determines WINDOW_UPDATE is not necessary at the
+// moment, it returns -1.
+int32_t determine_window_update_transmission(nghttp2_session *session,
+                                             int32_t stream_id);
+
+// Dumps name/value pairs in |nv| to |out|. The |nv| must be
+// terminated by nullptr.
+void dump_nv(FILE *out, const char **nv);
+
+// Dumps name/value pairs in |nva| to |out|.
+void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen);
+
+// Dumps name/value pairs in |nva| to |out|.
+void dump_nv(FILE *out, const Headers &nva);
+
+// Rewrites redirection URI which usually appears in location header
+// field. The |uri| is the URI in the location header field. The |u|
+// stores the result of parsed |uri|. The |request_authority| is the
+// host or :authority header field value in the request. The
+// |upstream_scheme| is either "https" or "http" in the upstream
+// interface.  Rewrite is done only if location header field value
+// contains |match_host| as host excluding port.  The |match_host| and
+// |request_authority| could be different.  If |request_authority| is
+// empty, strip authority.
+//
+// This function returns the new rewritten URI on success. If the
+// location URI is not subject to the rewrite, this function returns
+// emtpy string.
+std::string rewrite_location_uri(const std::string &uri,
+                                 const http_parser_url &u,
+                                 const std::string &match_host,
+                                 const std::string &request_authority,
+                                 const std::string &upstream_scheme);
+
+// Checks the header name/value pair using nghttp2_check_header_name()
+// and nghttp2_check_header_value(). If both function returns nonzero,
+// this function returns nonzero.
+int check_nv(const uint8_t *name, size_t namelen, const uint8_t *value,
+             size_t valuelen);
+
+// Returns parsed HTTP status code.  Returns -1 on failure.
+int parse_http_status_code(const std::string &src);
+
+// Header fields to be indexed, except HD_MAXIDX which is convenient
+// member to get maximum value.
+enum {
+  HD__AUTHORITY,
+  HD__HOST,
+  HD__METHOD,
+  HD__PATH,
+  HD__SCHEME,
+  HD__STATUS,
+  HD_ACCEPT_ENCODING,
+  HD_ACCEPT_LANGUAGE,
+  HD_ALT_SVC,
+  HD_CACHE_CONTROL,
+  HD_CONNECTION,
+  HD_CONTENT_LENGTH,
+  HD_COOKIE,
+  HD_EXPECT,
+  HD_HOST,
+  HD_HTTP2_SETTINGS,
+  HD_IF_MODIFIED_SINCE,
+  HD_KEEP_ALIVE,
+  HD_LINK,
+  HD_LOCATION,
+  HD_PROXY_CONNECTION,
+  HD_SERVER,
+  HD_TE,
+  HD_TRAILER,
+  HD_TRANSFER_ENCODING,
+  HD_UPGRADE,
+  HD_USER_AGENT,
+  HD_VIA,
+  HD_X_FORWARDED_FOR,
+  HD_X_FORWARDED_PROTO,
+  HD_MAXIDX,
+};
+
+using HeaderIndex = std::array<int16_t, HD_MAXIDX>;
+
+// Looks up header token for header name |name| of length |namelen|.
+// Only headers we are interested in are tokenized.  If header name
+// cannot be tokenized, returns -1.
+int lookup_token(const uint8_t *name, size_t namelen);
+int lookup_token(const std::string &name);
+
+// Initializes |hdidx|, header index.  The |hdidx| must point to the
+// array containing at least HD_MAXIDX elements.
+void init_hdidx(HeaderIndex &hdidx);
+// Indexes header |token| using index |idx|.
+void index_header(HeaderIndex &hdidx, int16_t token, size_t idx);
+
+// Returns true if HTTP/2 request pseudo header |token| is not indexed
+// yet and not -1.
+bool check_http2_request_pseudo_header(const HeaderIndex &hdidx, int16_t token);
+
+// Returns true if HTTP/2 response pseudo header |token| is not
+// indexed yet and not -1.
+bool check_http2_response_pseudo_header(const HeaderIndex &hdidx,
+                                        int16_t token);
+
+// Returns true if header field denoted by |token| is allowed for
+// HTTP/2.
+bool http2_header_allowed(int16_t token);
+
+// Returns true that |hdidx| contains mandatory HTTP/2 request
+// headers.
+bool http2_mandatory_request_headers_presence(const HeaderIndex &hdidx);
+
+// Returns header denoted by |token| using index |hdidx|.
+const Headers::value_type *get_header(const HeaderIndex &hdidx, int16_t token,
+                                      const Headers &nva);
+
+struct LinkHeader {
+  // The region of URI is [uri.first, uri.second).
+  std::pair<const char *, const char *> uri;
+};
+
+// Returns next URI-reference in Link header field value |src| of
+// length |len|.  If no URI-reference found after searching all input,
+// returned uri field is empty.  This imply that empty URI-reference
+// is ignored during parsing.
+std::vector<LinkHeader> parse_link_header(const char *src, size_t len);
+
+// Constructs path by combining base path |base_path| of length
+// |base_pathlen| with another path |rel_path| of length
+// |rel_pathlen|.  The base path and another path can have optional
+// query component.  This function assumes |base_path| is
+// cannibalized.  In other words, it does not contain ".." or "." path
+// components and starts with "/" if it is not empty.
+std::string path_join(const char *base_path, size_t base_pathlen,
+                      const char *base_query, size_t base_querylen,
+                      const char *rel_path, size_t rel_pathlen,
+                      const char *rel_query, size_t rel_querylen);
+
+} // namespace http2
+
+} // namespace nghttp2
+
+#endif // HTTP2_H
diff --git a/src/http2_test.cc b/src/http2_test.cc
new file mode 100644 (file)
index 0000000..d7df238
--- /dev/null
@@ -0,0 +1,666 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "http2_test.h"
+
+#include <cassert>
+#include <cstring>
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include "http-parser/http_parser.h"
+
+#include "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+#define MAKE_NV(K, V)                                                          \
+  {                                                                            \
+    (uint8_t *) K, (uint8_t *)V, sizeof(K) - 1, sizeof(V) - 1,                 \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+
+namespace shrpx {
+
+namespace {
+void check_nv(const Header &a, const nghttp2_nv *b) {
+  CU_ASSERT(a.name.size() == b->namelen);
+  CU_ASSERT(a.value.size() == b->valuelen);
+  CU_ASSERT(memcmp(a.name.c_str(), b->name, b->namelen) == 0);
+  CU_ASSERT(memcmp(a.value.c_str(), b->value, b->valuelen) == 0);
+}
+} // namespace
+
+void test_http2_add_header(void) {
+  auto nva = Headers();
+
+  http2::add_header(nva, (const uint8_t *)"alpha", 5, (const uint8_t *)"123", 3,
+                    false, -1);
+  CU_ASSERT(Headers::value_type("alpha", "123") == nva[0]);
+  CU_ASSERT(!nva[0].no_index);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"alpha", 5, (const uint8_t *)"", 0,
+                    true, -1);
+  CU_ASSERT(Headers::value_type("alpha", "") == nva[0]);
+  CU_ASSERT(nva[0].no_index);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)" b", 2,
+                    false, -1);
+  CU_ASSERT(Headers::value_type("a", "b") == nva[0]);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)"b ", 2,
+                    false, -1);
+  CU_ASSERT(Headers::value_type("a", "b") == nva[0]);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)"  b  ", 5,
+                    false, -1);
+  CU_ASSERT(Headers::value_type("a", "b") == nva[0]);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)"  bravo  ",
+                    9, false, -1);
+  CU_ASSERT(Headers::value_type("a", "bravo") == nva[0]);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"a", 1, (const uint8_t *)"    ", 4,
+                    false, -1);
+  CU_ASSERT(Headers::value_type("a", "") == nva[0]);
+
+  nva.clear();
+
+  http2::add_header(nva, (const uint8_t *)"te", 2, (const uint8_t *)"trailers",
+                    8, false, http2::HD_TE);
+  CU_ASSERT(http2::HD_TE == nva[0].token);
+}
+
+void test_http2_get_header(void) {
+  auto nva = Headers{{"alpha", "1"},
+                     {"bravo", "2"},
+                     {"bravo", "3"},
+                     {"charlie", "4"},
+                     {"delta", "5"},
+                     {"echo", "6"},
+                     {"content-length", "7"}};
+  const Headers::value_type *rv;
+  rv = http2::get_header(nva, "delta");
+  CU_ASSERT(rv != nullptr);
+  CU_ASSERT("delta" == rv->name);
+
+  rv = http2::get_header(nva, "bravo");
+  CU_ASSERT(rv != nullptr);
+  CU_ASSERT("bravo" == rv->name);
+
+  rv = http2::get_header(nva, "foxtrot");
+  CU_ASSERT(rv == nullptr);
+
+  http2::HeaderIndex hdidx;
+  http2::init_hdidx(hdidx);
+  hdidx[http2::HD_CONTENT_LENGTH] = 6;
+  rv = http2::get_header(hdidx, http2::HD_CONTENT_LENGTH, nva);
+  CU_ASSERT("content-length" == rv->name);
+}
+
+namespace {
+auto headers =
+    Headers{{"alpha", "0", true},
+            {"bravo", "1"},
+            {"connection", "2", false, http2::HD_CONNECTION},
+            {"connection", "3", false, http2::HD_CONNECTION},
+            {"delta", "4"},
+            {"expect", "5"},
+            {"foxtrot", "6"},
+            {"tango", "7"},
+            {"te", "8", false, http2::HD_TE},
+            {"te", "9", false, http2::HD_TE},
+            {"x-forwarded-proto", "10", false, http2::HD_X_FORWARDED_FOR},
+            {"x-forwarded-proto", "11", false, http2::HD_X_FORWARDED_FOR},
+            {"zulu", "12"}};
+} // namespace
+
+void test_http2_copy_headers_to_nva(void) {
+  std::vector<nghttp2_nv> nva;
+  http2::copy_headers_to_nva(nva, headers);
+  CU_ASSERT(9 == nva.size());
+  auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 8, 9, 12};
+  for (size_t i = 0; i < ans.size(); ++i) {
+    check_nv(headers[ans[i]], &nva[i]);
+
+    if (ans[i] == 0) {
+      CU_ASSERT(nva[i].flags & NGHTTP2_NV_FLAG_NO_INDEX);
+    } else {
+      CU_ASSERT(NGHTTP2_NV_FLAG_NONE == nva[i].flags);
+    }
+  }
+}
+
+void test_http2_build_http1_headers_from_headers(void) {
+  std::string hdrs;
+  http2::build_http1_headers_from_headers(hdrs, headers);
+  CU_ASSERT(hdrs == "Alpha: 0\r\n"
+                    "Bravo: 1\r\n"
+                    "Delta: 4\r\n"
+                    "Expect: 5\r\n"
+                    "Foxtrot: 6\r\n"
+                    "Tango: 7\r\n"
+                    "Te: 8\r\n"
+                    "Te: 9\r\n"
+                    "Zulu: 12\r\n");
+}
+
+void test_http2_lws(void) {
+  CU_ASSERT(!http2::lws("alpha"));
+  CU_ASSERT(http2::lws(" "));
+  CU_ASSERT(http2::lws(""));
+}
+
+namespace {
+void check_rewrite_location_uri(const std::string &want, const std::string &uri,
+                                const std::string &match_host,
+                                const std::string &req_authority,
+                                const std::string &upstream_scheme) {
+  http_parser_url u;
+  memset(&u, 0, sizeof(u));
+  CU_ASSERT(0 == http_parser_parse_url(uri.c_str(), uri.size(), 0, &u));
+  auto got = http2::rewrite_location_uri(uri, u, match_host, req_authority,
+                                         upstream_scheme);
+  CU_ASSERT(want == got);
+}
+} // namespace
+
+void test_http2_rewrite_location_uri(void) {
+  check_rewrite_location_uri("https://localhost:3000/alpha?bravo#charlie",
+                             "http://localhost:3001/alpha?bravo#charlie",
+                             "localhost:3001", "localhost:3000", "https");
+  check_rewrite_location_uri("https://localhost/", "http://localhost:3001/",
+                             "localhost", "localhost", "https");
+  check_rewrite_location_uri("http://localhost/", "http://localhost:3001/",
+                             "localhost", "localhost", "http");
+  check_rewrite_location_uri("http://localhost:443/", "http://localhost:3001/",
+                             "localhost", "localhost:443", "http");
+  check_rewrite_location_uri("https://localhost:80/", "http://localhost:3001/",
+                             "localhost", "localhost:80", "https");
+  check_rewrite_location_uri("", "http://localhost:3001/", "127.0.0.1",
+                             "127.0.0.1", "https");
+  check_rewrite_location_uri("https://localhost:3000/",
+                             "http://localhost:3001/", "localhost",
+                             "localhost:3000", "https");
+  check_rewrite_location_uri("https://localhost:3000/", "http://localhost/",
+                             "localhost", "localhost:3000", "https");
+
+  // match_host != req_authority
+  check_rewrite_location_uri("https://example.org", "http://127.0.0.1:8080",
+                             "127.0.0.1", "example.org", "https");
+  check_rewrite_location_uri("", "http://example.org", "127.0.0.1",
+                             "example.org", "https");
+}
+
+void test_http2_parse_http_status_code(void) {
+  CU_ASSERT(200 == http2::parse_http_status_code("200"));
+  CU_ASSERT(102 == http2::parse_http_status_code("102"));
+  CU_ASSERT(-1 == http2::parse_http_status_code("099"));
+  CU_ASSERT(-1 == http2::parse_http_status_code("99"));
+  CU_ASSERT(-1 == http2::parse_http_status_code("-1"));
+  CU_ASSERT(-1 == http2::parse_http_status_code("20a"));
+  CU_ASSERT(-1 == http2::parse_http_status_code(""));
+}
+
+void test_http2_index_header(void) {
+  http2::HeaderIndex hdidx;
+  http2::init_hdidx(hdidx);
+
+  http2::index_header(hdidx, http2::HD__AUTHORITY, 0);
+  http2::index_header(hdidx, -1, 1);
+
+  CU_ASSERT(0 == hdidx[http2::HD__AUTHORITY]);
+}
+
+void test_http2_lookup_token(void) {
+  CU_ASSERT(http2::HD__AUTHORITY == http2::lookup_token(":authority"));
+  CU_ASSERT(-1 == http2::lookup_token(":authorit"));
+  CU_ASSERT(-1 == http2::lookup_token(":Authority"));
+  CU_ASSERT(http2::HD_EXPECT == http2::lookup_token("expect"));
+}
+
+void test_http2_check_http2_pseudo_header(void) {
+  http2::HeaderIndex hdidx;
+  http2::init_hdidx(hdidx);
+
+  CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
+  hdidx[http2::HD__PATH] = 0;
+  CU_ASSERT(http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
+  hdidx[http2::HD__METHOD] = 1;
+  CU_ASSERT(
+      !http2::check_http2_request_pseudo_header(hdidx, http2::HD__METHOD));
+  CU_ASSERT(!http2::check_http2_request_pseudo_header(hdidx, http2::HD_VIA));
+
+  http2::init_hdidx(hdidx);
+
+  CU_ASSERT(
+      http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS));
+  hdidx[http2::HD__STATUS] = 0;
+  CU_ASSERT(
+      !http2::check_http2_response_pseudo_header(hdidx, http2::HD__STATUS));
+  CU_ASSERT(!http2::check_http2_response_pseudo_header(hdidx, http2::HD_VIA));
+}
+
+void test_http2_http2_header_allowed(void) {
+  CU_ASSERT(http2::http2_header_allowed(http2::HD__PATH));
+  CU_ASSERT(http2::http2_header_allowed(http2::HD_CONTENT_LENGTH));
+  CU_ASSERT(!http2::http2_header_allowed(http2::HD_CONNECTION));
+}
+
+void test_http2_mandatory_request_headers_presence(void) {
+  http2::HeaderIndex hdidx;
+  http2::init_hdidx(hdidx);
+
+  CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
+  hdidx[http2::HD__AUTHORITY] = 0;
+  CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
+  hdidx[http2::HD__METHOD] = 1;
+  CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
+  hdidx[http2::HD__PATH] = 2;
+  CU_ASSERT(!http2::http2_mandatory_request_headers_presence(hdidx));
+  hdidx[http2::HD__SCHEME] = 3;
+  CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx));
+
+  hdidx[http2::HD__AUTHORITY] = -1;
+  hdidx[http2::HD_HOST] = 0;
+  CU_ASSERT(http2::http2_mandatory_request_headers_presence(hdidx));
+}
+
+void test_http2_parse_link_header(void) {
+  {
+    // only URI appears; we don't extract URI unless it bears rel=preload
+    const char s[] = "<url>";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // URI url should be extracted
+    const char s[] = "<url>; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // With extra link-param.  URI url should be extracted
+    const char s[] = "<url>; rel=preload; as=file";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // With extra link-param.  URI url should be extracted
+    const char s[] = "<url>; as=file; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // With extra link-param and quote-string.  URI url should be
+    // extracted
+    const char s[] = R"(<url>; rel=preload; title="foo,bar")";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // With extra link-param and quote-string.  URI url should be
+    // extracted
+    const char s[] = R"(<url>; title="foo,bar"; rel=preload)";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // ',' after quote-string
+    const char s[] = R"(<url>; title="foo,bar", <url>; rel=preload)";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[25], &s[28]) == res[0].uri);
+  }
+  {
+    // Only first URI should be extracted.
+    const char s[] = "<url>; rel=preload, <url>";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // Both have rel=preload, so both urls should be extracted
+    const char s[] = "<url>; rel=preload, <url>; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(2 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+    CU_ASSERT(std::make_pair(&s[21], &s[24]) == res[1].uri);
+  }
+  {
+    // Second URI uri should be extracted.
+    const char s[] = "<url>, <url>;rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[8], &s[11]) == res[0].uri);
+  }
+  {
+    // Error if input ends with ';'
+    const char s[] = "<url>;rel=preload;";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // OK if input ends with ','
+    const char s[] = "<url>;rel=preload,";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // Multiple repeated ','s between fields is OK
+    const char s[] = "<url>,,,<url>;rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[9], &s[12]) == res[0].uri);
+  }
+  {
+    // Error if url is not enclosed by <>
+    const char s[] = "url>;rel=preload;";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Error if url is not enclosed by <>
+    const char s[] = "<url;rel=preload;";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Empty parameter value is not allowed
+    const char s[] = "<url>;rel=preload; as=";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Empty parameter value is not allowed
+    const char s[] = "<url>;as=;rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Empty parameter value is not allowed
+    const char s[] = "<url>;as=, <url>;rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Empty parameter name is not allowed
+    const char s[] = "<url>; =file; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Without whitespaces
+    const char s[] = "<url>;as=file;rel=preload,<url>;rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(2 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+    CU_ASSERT(std::make_pair(&s[27], &s[30]) == res[1].uri);
+  }
+  {
+    // link-extension may have no value
+    const char s[] = "<url>; as; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // ext-name-star
+    const char s[] = "<url>; foo*=bar; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri);
+  }
+  {
+    // '*' is not allowed expect for trailing one
+    const char s[] = "<url>; *=bar; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // '*' is not allowed expect for trailing one
+    const char s[] = "<url>; foo*bar=buzz; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // ext-name-star must be followed by '='
+    const char s[] = "<url>; foo*; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // '>' is not followed by ';'
+    const char s[] = "<url> rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+  {
+    // Starting with whitespace is no problem.
+    const char s[] = "  <url>; rel=preload";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(1 == res.size());
+    CU_ASSERT(std::make_pair(&s[3], &s[6]) == res[0].uri);
+  }
+  {
+    // preload is a prefix of bogus rel parameter value
+    const char s[] = "<url>; rel=preloadx";
+    auto res = http2::parse_link_header(s, sizeof(s) - 1);
+    CU_ASSERT(0 == res.size());
+  }
+}
+
+void test_http2_path_join(void) {
+  {
+    const char base[] = "/";
+    const char rel[] = "/";
+    CU_ASSERT("/" == http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                                      sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    const char base[] = "/";
+    const char rel[] = "/alpha";
+    CU_ASSERT("/alpha" == http2::path_join(base, sizeof(base) - 1, nullptr, 0,
+                                           rel, sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // rel ends with trailing '/'
+    const char base[] = "/";
+    const char rel[] = "/alpha/";
+    CU_ASSERT("/alpha/" == http2::path_join(base, sizeof(base) - 1, nullptr, 0,
+                                            rel, sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // rel contains multiple components
+    const char base[] = "/";
+    const char rel[] = "/alpha/bravo";
+    CU_ASSERT("/alpha/bravo" == http2::path_join(base, sizeof(base) - 1,
+                                                 nullptr, 0, rel,
+                                                 sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // rel is relative
+    const char base[] = "/";
+    const char rel[] = "alpha/bravo";
+    CU_ASSERT("/alpha/bravo" == http2::path_join(base, sizeof(base) - 1,
+                                                 nullptr, 0, rel,
+                                                 sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // rel is relative and base ends without /, which means it refers
+    // to file.
+    const char base[] = "/alpha";
+    const char rel[] = "bravo/charlie";
+    CU_ASSERT("/bravo/charlie" ==
+              http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                               sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // rel contains repeated '/'s
+    const char base[] = "/";
+    const char rel[] = "/alpha/////bravo/////";
+    CU_ASSERT("/alpha/bravo/" == http2::path_join(base, sizeof(base) - 1,
+                                                  nullptr, 0, rel,
+                                                  sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // base ends with '/', so '..' eats 'bravo'
+    const char base[] = "/alpha/bravo/";
+    const char rel[] = "../charlie/delta";
+    CU_ASSERT("/alpha/charlie/delta" ==
+              http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                               sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // base does not end with '/', so '..' eats 'alpha/bravo'
+    const char base[] = "/alpha/bravo";
+    const char rel[] = "../charlie";
+    CU_ASSERT("/charlie" == http2::path_join(base, sizeof(base) - 1, nullptr, 0,
+                                             rel, sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // 'charlie' is eaten by following '..'
+    const char base[] = "/alpha/bravo/";
+    const char rel[] = "../charlie/../delta";
+    CU_ASSERT("/alpha/delta" == http2::path_join(base, sizeof(base) - 1,
+                                                 nullptr, 0, rel,
+                                                 sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // excessive '..' results in '/'
+    const char base[] = "/alpha/bravo/";
+    const char rel[] = "../../../";
+    CU_ASSERT("/" == http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                                      sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // excessive '..'  and  path component
+    const char base[] = "/alpha/bravo/";
+    const char rel[] = "../../../charlie";
+    CU_ASSERT("/charlie" == http2::path_join(base, sizeof(base) - 1, nullptr, 0,
+                                             rel, sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // rel ends with '..'
+    const char base[] = "/alpha/bravo/";
+    const char rel[] = "charlie/..";
+    CU_ASSERT("/alpha/bravo/" == http2::path_join(base, sizeof(base) - 1,
+                                                  nullptr, 0, rel,
+                                                  sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // base empty and rel contains '..'
+    const char base[] = "";
+    const char rel[] = "charlie/..";
+    CU_ASSERT("/" == http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                                      sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // '.' is ignored
+    const char base[] = "/";
+    const char rel[] = "charlie/././././delta";
+    CU_ASSERT("/charlie/delta" ==
+              http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                               sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // trailing '.' is ignored
+    const char base[] = "/";
+    const char rel[] = "charlie/.";
+    CU_ASSERT("/charlie/" == http2::path_join(base, sizeof(base) - 1, nullptr,
+                                              0, rel, sizeof(rel) - 1, nullptr,
+                                              0));
+  }
+  {
+    // query
+    const char base[] = "/";
+    const char rel[] = "/";
+    const char relq[] = "q";
+    CU_ASSERT("/?q" == http2::path_join(base, sizeof(base) - 1, nullptr, 0, rel,
+                                        sizeof(rel) - 1, relq,
+                                        sizeof(relq) - 1));
+  }
+  {
+    // empty rel and query
+    const char base[] = "/alpha";
+    const char rel[] = "";
+    const char relq[] = "q";
+    CU_ASSERT("/alpha?q" == http2::path_join(base, sizeof(base) - 1, nullptr, 0,
+                                             rel, sizeof(rel) - 1, relq,
+                                             sizeof(relq) - 1));
+  }
+  {
+    // both rel and query are empty
+    const char base[] = "/alpha";
+    const char baseq[] = "r";
+    const char rel[] = "";
+    const char relq[] = "";
+    CU_ASSERT("/alpha?r" ==
+              http2::path_join(base, sizeof(base) - 1, baseq, sizeof(baseq) - 1,
+                               rel, sizeof(rel) - 1, relq, sizeof(relq) - 1));
+  }
+  {
+    // empty base
+    const char base[] = "";
+    const char rel[] = "/alpha";
+    CU_ASSERT("/alpha" == http2::path_join(base, sizeof(base) - 1, nullptr, 0,
+                                           rel, sizeof(rel) - 1, nullptr, 0));
+  }
+  {
+    // everything is empty
+    CU_ASSERT("/" ==
+              http2::path_join(nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0));
+  }
+  {
+    // only baseq is not empty
+    const char base[] = "";
+    const char baseq[] = "r";
+    const char rel[] = "";
+    CU_ASSERT("/?r" == http2::path_join(base, sizeof(base) - 1, baseq,
+                                        sizeof(baseq) - 1, rel, sizeof(rel) - 1,
+                                        nullptr, 0));
+  }
+}
+
+} // namespace shrpx
diff --git a/src/http2_test.h b/src/http2_test.h
new file mode 100644 (file)
index 0000000..1171b92
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_TEST_H
+#define SHRPX_HTTP2_TEST_H
+
+namespace shrpx {
+
+void test_http2_add_header(void);
+void test_http2_get_header(void);
+void test_http2_copy_headers_to_nva(void);
+void test_http2_build_http1_headers_from_headers(void);
+void test_http2_lws(void);
+void test_http2_rewrite_location_uri(void);
+void test_http2_parse_http_status_code(void);
+void test_http2_index_header(void);
+void test_http2_lookup_token(void);
+void test_http2_check_http2_pseudo_header(void);
+void test_http2_http2_header_allowed(void);
+void test_http2_mandatory_request_headers_presence(void);
+void test_http2_parse_link_header(void);
+void test_http2_path_join(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_TEST_H
diff --git a/src/includes/Makefile.am b/src/includes/Makefile.am
new file mode 100644 (file)
index 0000000..e642d5f
--- /dev/null
@@ -0,0 +1,23 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2014 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+nobase_include_HEADERS = nghttp2/asio_http2.h
diff --git a/src/includes/nghttp2/asio_http2.h b/src/includes/nghttp2/asio_http2.h
new file mode 100644 (file)
index 0000000..c09b7ac
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef ASIO_HTTP2_H
+#define ASIO_HTTP2_H
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+#include <functional>
+
+namespace nghttp2 {
+
+namespace asio_http2 {
+
+struct header {
+  std::string name;
+  std::string value;
+};
+
+typedef std::function<void(const uint8_t *, std::size_t)> data_cb;
+typedef std::function<void(void)> void_cb;
+
+// Callback function to generate response body.  The implementation of
+// this callback must fill at most |len| bytes data to |buf|.  The
+// return value is pair of written bytes and bool value indicating
+// that this is the end of the body.  If the end of the body was
+// reached, return true.  If there is error and application wants to
+// terminate stream, return std::make_pair(-1, false).  Returning
+// std::make_pair(0, false) tells the library that don't call this
+// callback until application calls response::resume().  This is
+// useful when there is no data to send at the moment but there will
+// be more to come in near future.
+typedef std::function<std::pair<ssize_t, bool>(uint8_t *buf, std::size_t len)>
+    read_cb;
+
+class channel_impl;
+
+class channel {
+public:
+  // Application must not call this directly.
+  channel();
+
+  // Schedules the execution of callback |cb| in the same thread where
+  // request callback is called.  Therefore, it is same to use request
+  // or response object in |cb|.  The callbacks are executed in the
+  // same order they are posted though same channel object if they are
+  // posted from the same thread.
+  void post(void_cb cb);
+
+  // Application must not call this directly.
+  channel_impl &impl();
+
+private:
+  std::unique_ptr<channel_impl> impl_;
+};
+
+typedef std::function<void(channel &)> thread_cb;
+
+namespace server {
+
+class request_impl;
+class response_impl;
+
+class request {
+public:
+  // Application must not call this directly.
+  request();
+
+  // Returns request headers.  The pusedo headers, which start with
+  // colon (;), are exluced from this list.
+  const std::vector<header> &headers() const;
+
+  // Returns method (e.g., GET).
+  const std::string &method() const;
+
+  // Returns scheme (e.g., https).
+  const std::string &scheme() const;
+
+  // Returns authority (e.g., example.org).  This could be empty
+  // string.  In this case, check host().
+
+  const std::string &authority() const;
+  // Returns host (e.g., example.org).  If host header field is not
+  // present, this value is copied from authority().
+
+  const std::string &host() const;
+
+  // Returns path (e.g., /index.html).
+  const std::string &path() const;
+
+  // Sets callback when chunk of request body is received.
+  void on_data(data_cb cb);
+
+  // Sets callback when request was completed.
+  void on_end(void_cb cb);
+
+  // Pushes resource denoted by |path| using |method|.  The additional
+  // headers can be given in |headers|.  request_cb will be called for
+  // pushed resource later on.  This function returns true if it
+  // succeeds, or false.
+  bool push(std::string method, std::string path,
+            std::vector<header> headers = {});
+
+  // Returns true if this is pushed request.
+  bool pushed() const;
+
+  // Returns true if stream has been closed.
+  bool closed() const;
+
+  // Runs function |start| in one of background threads.  Returns true
+  // if scheduling task was done successfully.
+  //
+  // Since |start| is called in different thread, calling any method
+  // of request or response object in the callback may cause undefined
+  // behavior.  To safely use them, use channel::post().  A callback
+  // passed to channel::post() is executed in the same thread where
+  // request callback is called, so it is safe to use request or
+  // response object.  Example::
+  bool run_task(thread_cb start);
+
+  // Application must not call this directly.
+  request_impl &impl();
+
+private:
+  std::unique_ptr<request_impl> impl_;
+};
+
+class response {
+public:
+  // Application must not call this directly.
+  response();
+
+  // Write response header using |status_code| (e.g., 200) and
+  // additional headers in |headers|.
+  void write_head(unsigned int status_code, std::vector<header> headers = {});
+
+  // Sends |data| as request body.  No further call of end() is
+  // allowed.
+  void end(std::string data = "");
+
+  // Sets callback |cb| as a generator of the response body.  No
+  // further call of end() is allowed.
+  void end(read_cb cb);
+
+  // Resumes deferred response.
+  void resume();
+
+  // Returns status code.
+  unsigned int status_code() const;
+
+  // Returns true if response has been started.
+  bool started() const;
+
+  // Application must not call this directly.
+  response_impl &impl();
+
+private:
+  std::unique_ptr<response_impl> impl_;
+};
+
+// This is so called request callback.  Called every time request is
+// received.
+typedef std::function<void(const std::shared_ptr<request> &,
+                           const std::shared_ptr<response> &)> request_cb;
+
+class http2_impl;
+
+class http2 {
+public:
+  http2();
+  ~http2();
+
+  // Starts listening connection on given address and port.  The
+  // incoming requests are handled by given callback |cb|.
+  void listen(const std::string &address, uint16_t port, request_cb cb);
+
+  // Sets number of native threads to handle incoming HTTP request.
+  // It defaults to 1.
+  void num_threads(size_t num_threads);
+
+  // Sets TLS private key file and certificate file.  Both files must
+  // be in PEM format.
+  void tls(std::string private_key_file, std::string certificate_file);
+
+  // Sets number of background threads to run concurrent tasks (see
+  // request::run_task()).  It defaults to 1.  This is not the number
+  // of thread to handle incoming HTTP request.  For this purpose, see
+  // num_threads().
+  void num_concurrent_tasks(size_t num_concurrent_tasks);
+
+  // Sets the maximum length to which the queue of pending
+  // connections.
+  void backlog(int backlog);
+
+private:
+  std::unique_ptr<http2_impl> impl_;
+};
+
+} // namespace server
+
+// Convenient function to create function to read file denoted by
+// |path|.  This can be passed to response::end().
+read_cb file_reader(const std::string &path);
+
+// Like file_reader(const std::string&), but it takes opened file
+// descriptor.  The passed descriptor will be closed when returned
+// function object is destroyed.
+read_cb file_reader_from_fd(int fd);
+
+// Validates path so that it does not contain directory traversal
+// vector.  Returns true if path is safe.  The |path| must start with
+// "/" otherwise returns false.  This function should be called after
+// percent-decode was performed.
+bool check_path(const std::string &path);
+
+// Performs percent-decode against string |s|.
+std::string percent_decode(const std::string &s);
+
+// Returns HTTP date representation of current posix time |t|.
+std::string http_date(int64_t t);
+
+} // namespace asio_http2
+
+} // namespace nghttp2
+
+#endif // ASIO_HTTP2_H
diff --git a/src/inflatehd.cc b/src/inflatehd.cc
new file mode 100644 (file)
index 0000000..353941a
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <unistd.h>
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <assert.h>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+extern "C" {
+
+#include "nghttp2_hd.h"
+#include "nghttp2_frame.h"
+
+#include "comp_helper.h"
+}
+
+typedef struct { int dump_header_table; } inflate_config;
+
+static inflate_config config;
+
+static uint8_t to_ud(char c) {
+  if (c >= 'A' && c <= 'Z') {
+    return c - 'A' + 10;
+  } else if (c >= 'a' && c <= 'z') {
+    return c - 'a' + 10;
+  } else {
+    return c - '0';
+  }
+}
+
+static void decode_hex(uint8_t *dest, const char *src, size_t len) {
+  size_t i;
+  for (i = 0; i < len; i += 2) {
+    *dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]);
+  }
+}
+
+static void to_json(nghttp2_hd_inflater *inflater, json_t *headers,
+                    json_t *wire, int seq, size_t old_settings_table_size) {
+  auto obj = json_object();
+  json_object_set_new(obj, "seq", json_integer(seq));
+  json_object_set(obj, "wire", wire);
+  json_object_set(obj, "headers", headers);
+  if (old_settings_table_size != inflater->settings_hd_table_bufsize_max) {
+    json_object_set_new(obj, "header_table_size",
+                        json_integer(inflater->settings_hd_table_bufsize_max));
+  }
+  if (config.dump_header_table) {
+    json_object_set_new(obj, "header_table", dump_header_table(&inflater->ctx));
+  }
+  json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER);
+  json_decref(obj);
+  printf("\n");
+}
+
+static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) {
+  ssize_t rv;
+  nghttp2_nv nv;
+  int inflate_flags;
+  size_t old_settings_table_size = inflater->settings_hd_table_bufsize_max;
+
+  auto wire = json_object_get(obj, "wire");
+
+  if (wire == nullptr) {
+    fprintf(stderr, "'wire' key is missing at %d\n", seq);
+    return -1;
+  }
+
+  auto table_size = json_object_get(obj, "header_table_size");
+
+  if (table_size) {
+    if (!json_is_integer(table_size)) {
+      fprintf(stderr,
+              "The value of 'header_table_size key' is not integer at %d\n",
+              seq);
+      return -1;
+    }
+    rv = nghttp2_hd_inflate_change_table_size(inflater,
+                                              json_integer_value(table_size));
+    if (rv != 0) {
+      fprintf(stderr,
+              "nghttp2_hd_change_table_size() failed with error %s at %d\n",
+              nghttp2_strerror(rv), seq);
+      return -1;
+    }
+  }
+
+  auto inputlen = strlen(json_string_value(wire));
+
+  if (inputlen & 1) {
+    fprintf(stderr, "Badly formatted output value at %d\n", seq);
+    exit(EXIT_FAILURE);
+  }
+
+  auto buflen = inputlen / 2;
+  auto buf = std::vector<uint8_t>(buflen);
+
+  decode_hex(buf.data(), json_string_value(wire), inputlen);
+
+  auto headers = json_array();
+
+  auto p = buf.data();
+  for (;;) {
+    inflate_flags = 0;
+    rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1);
+    if (rv < 0) {
+      fprintf(stderr, "inflate failed with error code %zd at %d\n", rv, seq);
+      exit(EXIT_FAILURE);
+    }
+    p += rv;
+    buflen -= rv;
+    if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+      json_array_append_new(
+          headers, dump_header(nv.name, nv.namelen, nv.value, nv.valuelen));
+    }
+    if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+      break;
+    }
+  }
+  assert(buflen == 0);
+  nghttp2_hd_inflate_end_headers(inflater);
+  to_json(inflater, headers, wire, seq, old_settings_table_size);
+  json_decref(headers);
+
+  return 0;
+}
+
+static int perform(void) {
+  nghttp2_hd_inflater *inflater = NULL;
+  json_error_t error;
+
+  auto json = json_loadf(stdin, 0, &error);
+
+  if (json == nullptr) {
+    fprintf(stderr, "JSON loading failed\n");
+    exit(EXIT_FAILURE);
+  }
+
+  auto cases = json_object_get(json, "cases");
+
+  if (cases == nullptr) {
+    fprintf(stderr, "Missing 'cases' key in root object\n");
+    exit(EXIT_FAILURE);
+  }
+
+  if (!json_is_array(cases)) {
+    fprintf(stderr, "'cases' must be JSON array\n");
+    exit(EXIT_FAILURE);
+  }
+
+  nghttp2_hd_inflate_new(&inflater);
+  output_json_header();
+  auto len = json_array_size(cases);
+
+  for (size_t i = 0; i < len; ++i) {
+    auto obj = json_array_get(cases, i);
+    if (!json_is_object(obj)) {
+      fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i);
+      continue;
+    }
+    if (inflate_hd(obj, inflater, i) != 0) {
+      continue;
+    }
+    if (i + 1 < len) {
+      printf(",\n");
+    }
+  }
+  output_json_footer();
+  nghttp2_hd_inflate_del(inflater);
+  json_decref(json);
+
+  return 0;
+}
+
+static void print_help(void) {
+  std::cout << R"(HPACK HTTP/2 header decoder
+Usage: inflatehd [OPTIONS] < INPUT
+
+Reads JSON  data from stdin  and outputs inflated name/value  pairs in
+JSON.
+
+The root JSON object must contain "context" key, which indicates which
+compression context is used.  If  it is "request", request compression
+context  is used.   Otherwise, response  compression context  is used.
+The value  of "cases" key  contains the sequence of  compressed header
+block.  They share  the same compression context and  are processed in
+the order they appear.  Each item in the sequence is a JSON object and
+it must  have at least "wire"  key.  Its value is  a string containing
+compressed header block in hex string.
+
+Example:
+
+{
+  "context": "request",
+  "cases":
+  [
+    { "wire": "0284f77778ff" },
+    { "wire": "0185fafd3c3c7f81" }
+  ]
+}
+
+The output of this program can be used as input for deflatehd.
+
+OPTIONS:
+    -d, --dump-header-table
+                      Output dynamic header table.)" << std::endl;
+  ;
+}
+
+static struct option long_options[] = {
+    {"dump-header-table", no_argument, nullptr, 'd'}, {nullptr, 0, nullptr, 0}};
+
+int main(int argc, char **argv) {
+  config.dump_header_table = 0;
+  while (1) {
+    int option_index = 0;
+    int c = getopt_long(argc, argv, "dh", long_options, &option_index);
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+    case 'h':
+      print_help();
+      exit(EXIT_SUCCESS);
+    case 'd':
+      // --dump-header-table
+      config.dump_header_table = 1;
+      break;
+    case '?':
+      exit(EXIT_FAILURE);
+    default:
+      break;
+    }
+  }
+  perform();
+  return 0;
+}
diff --git a/src/libevent_util.cc b/src/libevent_util.cc
new file mode 100644 (file)
index 0000000..23edea5
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "libevent_util.h"
+
+#include <cstring>
+#include <algorithm>
+
+namespace nghttp2 {
+
+namespace util {
+
+EvbufferBuffer::EvbufferBuffer()
+    : evbuffer_(nullptr), bucket_(nullptr), buf_(nullptr), bufmax_(0),
+      buflen_(0), limit_(0), writelen_(0) {}
+
+EvbufferBuffer::EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+                               ssize_t limit)
+    : evbuffer_(evbuffer), bucket_(limit == -1 ? nullptr : evbuffer_new()),
+      buf_(buf), bufmax_(bufmax), buflen_(0), limit_(limit), writelen_(0) {}
+
+void EvbufferBuffer::reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+                           ssize_t limit) {
+  evbuffer_ = evbuffer;
+  buf_ = buf;
+  if (limit != -1 && !bucket_) {
+    bucket_ = evbuffer_new();
+  }
+  bufmax_ = bufmax;
+  buflen_ = 0;
+  limit_ = limit;
+  writelen_ = 0;
+}
+
+EvbufferBuffer::~EvbufferBuffer() {
+  if (bucket_) {
+    evbuffer_free(bucket_);
+  }
+}
+
+int EvbufferBuffer::write_buffer() {
+  for (auto pos = buf_, end = buf_ + buflen_; pos < end;) {
+    // To avoid merging chunks in evbuffer, we first add to temporal
+    // buffer bucket_ and then move its chain to evbuffer_.
+    auto nwrite = std::min(end - pos, limit_);
+    auto rv = evbuffer_add(bucket_, pos, nwrite);
+    if (rv == -1) {
+      return -1;
+    }
+    rv = evbuffer_add_buffer(evbuffer_, bucket_);
+    if (rv == -1) {
+      return -1;
+    }
+    pos += nwrite;
+  }
+  return 0;
+}
+
+int EvbufferBuffer::flush() {
+  int rv;
+  if (buflen_ > 0) {
+    if (limit_ == -1) {
+      rv = evbuffer_add(evbuffer_, buf_, buflen_);
+    } else {
+      rv = write_buffer();
+    }
+    if (rv == -1) {
+      return -1;
+    }
+    writelen_ += buflen_;
+    buflen_ = 0;
+  }
+  return 0;
+}
+
+int EvbufferBuffer::add(const uint8_t *data, size_t datalen) {
+  int rv;
+  if (buflen_ + datalen > bufmax_) {
+    if (buflen_ > 0) {
+      if (limit_ == -1) {
+        rv = evbuffer_add(evbuffer_, buf_, buflen_);
+      } else {
+        rv = write_buffer();
+      }
+      if (rv == -1) {
+        return -1;
+      }
+      writelen_ += buflen_;
+      buflen_ = 0;
+    }
+    if (datalen > bufmax_) {
+      if (limit_ == -1) {
+        rv = evbuffer_add(evbuffer_, data, datalen);
+      } else {
+        rv = write_buffer();
+      }
+      if (rv == -1) {
+        return -1;
+      }
+      writelen_ += buflen_;
+      return 0;
+    }
+  }
+  memcpy(buf_ + buflen_, data, datalen);
+  buflen_ += datalen;
+  return 0;
+}
+
+size_t EvbufferBuffer::get_buflen() const { return buflen_; }
+
+size_t EvbufferBuffer::get_writelen() const { return writelen_; }
+
+void bev_enable_unless(bufferevent *bev, int events) {
+  if ((bufferevent_get_enabled(bev) & events) == events) {
+    return;
+  }
+
+  bufferevent_enable(bev, events);
+}
+
+void bev_disable_unless(bufferevent *bev, int events) {
+  if ((bufferevent_get_enabled(bev) & events) == 0) {
+    return;
+  }
+
+  bufferevent_disable(bev, events);
+}
+
+} // namespace util
+
+} // namespace nghttp2
diff --git a/src/libevent_util.h b/src/libevent_util.h
new file mode 100644 (file)
index 0000000..1d1ee91
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef LIBEVENT_UTIL_H
+#define LIBEVENT_UTIL_H
+
+#include "nghttp2_config.h"
+
+#include <event2/buffer.h>
+#include <event2/bufferevent.h>
+
+namespace nghttp2 {
+
+namespace util {
+
+class EvbufferBuffer {
+public:
+  EvbufferBuffer();
+  // If |limit| is not -1, at most min(limit, bufmax) size bytes are
+  // added to evbuffer_.
+  EvbufferBuffer(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+                 ssize_t limit = -1);
+  ~EvbufferBuffer();
+  void reset(evbuffer *evbuffer, uint8_t *buf, size_t bufmax,
+             ssize_t limit = -1);
+  int flush();
+  int add(const uint8_t *data, size_t datalen);
+  size_t get_buflen() const;
+  int write_buffer();
+  // Returns the number of written bytes to evbuffer_ so far.  reset()
+  // resets this value to 0.
+  size_t get_writelen() const;
+
+private:
+  evbuffer *evbuffer_;
+  evbuffer *bucket_;
+  uint8_t *buf_;
+  size_t bufmax_;
+  size_t buflen_;
+  ssize_t limit_;
+  size_t writelen_;
+};
+
+// These functions are provided to reduce epoll_ctl syscall.  Avoid
+// calling bufferevent_enable/disable() unless it is required by
+// sniffing current enabled events.
+void bev_enable_unless(bufferevent *bev, int events);
+void bev_disable_unless(bufferevent *bev, int events);
+
+} // namespace util
+
+} // namespace nghttp2
+
+#endif // LIBEVENT_UTIL_H
diff --git a/src/libnghttp2_asio.pc.in b/src/libnghttp2_asio.pc.in
new file mode 100644 (file)
index 0000000..ec4fbbb
--- /dev/null
@@ -0,0 +1,33 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2014 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libnghttp2_asio
+Description: HTTP/2 C++ library
+URL: https://github.com/tatsuhiro-t/nghttp2
+Version: @VERSION@
+Libs: -L${libdir} -lnghttp2_asio
+Cflags: -I${includedir}
diff --git a/src/memchunk.h b/src/memchunk.h
new file mode 100644 (file)
index 0000000..d2e7a6c
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MEMCHUNK_H
+#define MEMCHUNK_H
+
+#include "nghttp2_config.h"
+
+#include <sys/uio.h>
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <array>
+#include <algorithm>
+
+#include "template.h"
+
+namespace nghttp2 {
+
+template <size_t N> struct Memchunk {
+  Memchunk(std::unique_ptr<Memchunk> next_chunk)
+      : pos(std::begin(buf)), last(pos), knext(std::move(next_chunk)),
+        kprev(nullptr), next(nullptr) {
+    if (knext) {
+      knext->kprev = this;
+    }
+  }
+  size_t len() const { return last - pos; }
+  size_t left() const { return std::end(buf) - last; }
+  void reset() { pos = last = std::begin(buf); }
+  std::array<uint8_t, N> buf;
+  uint8_t *pos, *last;
+  std::unique_ptr<Memchunk> knext;
+  Memchunk *kprev;
+  Memchunk *next;
+  static const size_t size = N;
+};
+
+template <typename T> struct Pool {
+  Pool() : pool(nullptr), freelist(nullptr), poolsize(0) {}
+  T *get() {
+    if (freelist) {
+      auto m = freelist;
+      freelist = freelist->next;
+      m->next = nullptr;
+      m->reset();
+      return m;
+    }
+
+    pool = make_unique<T>(std::move(pool));
+    poolsize += T::size;
+    return pool.get();
+  }
+  void recycle(T *m) {
+    if (freelist) {
+      m->next = freelist;
+    } else {
+      m->next = nullptr;
+    }
+    freelist = m;
+  }
+  void shrink(size_t max) {
+    auto m = freelist;
+    for (; m && poolsize > max;) {
+      auto next = m->next;
+      poolsize -= T::size;
+      auto p = m->kprev;
+      if (p) {
+        p->knext = std::move(m->knext);
+        if (p->knext) {
+          p->knext->kprev = p;
+        }
+      } else {
+        pool = std::move(m->knext);
+        if (pool) {
+          pool->kprev = nullptr;
+        }
+      }
+      m = next;
+    }
+    freelist = m;
+  }
+  using value_type = T;
+  std::unique_ptr<T> pool;
+  T *freelist;
+  size_t poolsize;
+};
+
+template <typename Memchunk> struct Memchunks {
+  Memchunks(Pool<Memchunk> *pool)
+      : pool(pool), head(nullptr), tail(nullptr), len(0) {}
+  ~Memchunks() {
+    if (!pool) {
+      return;
+    }
+    for (auto m = head; m;) {
+      auto next = m->next;
+      pool->recycle(m);
+      m = next;
+    }
+  }
+  size_t append(const void *src, size_t count) {
+    if (count == 0) {
+      return 0;
+    }
+
+    auto first = static_cast<const uint8_t *>(src);
+    auto last = first + count;
+
+    if (!tail) {
+      head = tail = pool->get();
+    }
+
+    for (;;) {
+      auto n = std::min(static_cast<size_t>(last - first), tail->left());
+      tail->last = std::copy_n(first, n, tail->last);
+      first += n;
+      len += n;
+      if (first == last) {
+        break;
+      }
+
+      tail->next = pool->get();
+      tail = tail->next;
+    }
+
+    return count;
+  }
+  template <size_t N> size_t append(const char (&s)[N]) {
+    return append(s, N - 1);
+  }
+  size_t remove(void *dest, size_t count) {
+    if (!tail || count == 0) {
+      return 0;
+    }
+
+    auto first = static_cast<uint8_t *>(dest);
+    auto last = first + count;
+
+    auto m = head;
+
+    while (m) {
+      auto next = m->next;
+      auto n = std::min(static_cast<size_t>(last - first), m->len());
+
+      assert(m->len());
+      first = std::copy_n(m->pos, n, first);
+      m->pos += n;
+      len -= n;
+      if (m->len() > 0) {
+        break;
+      }
+      pool->recycle(m);
+      m = next;
+    }
+    head = m;
+    if (head == nullptr) {
+      tail = nullptr;
+    }
+
+    return first - static_cast<uint8_t *>(dest);
+  }
+  size_t drain(size_t count) {
+    auto ndata = count;
+    auto m = head;
+    while (m) {
+      auto next = m->next;
+      auto n = std::min(count, m->len());
+      m->pos += n;
+      count -= n;
+      len -= n;
+      if (m->len() > 0) {
+        break;
+      }
+
+      pool->recycle(m);
+      m = next;
+    }
+    head = m;
+    if (head == nullptr) {
+      tail = nullptr;
+    }
+    return ndata - count;
+  }
+  int riovec(struct iovec *iov, int iovcnt) {
+    if (!head) {
+      return 0;
+    }
+    auto m = head;
+    int i;
+    for (i = 0; i < iovcnt && m; ++i, m = m->next) {
+      iov[i].iov_base = m->pos;
+      iov[i].iov_len = m->len();
+    }
+    return i;
+  }
+  size_t rleft() const { return len; }
+
+  Pool<Memchunk> *pool;
+  Memchunk *head, *tail;
+  size_t len;
+};
+
+using Memchunk16K = Memchunk<16384>;
+using MemchunkPool = Pool<Memchunk16K>;
+using DefaultMemchunks = Memchunks<Memchunk16K>;
+
+#define DEFAULT_WR_IOVCNT 16
+
+#if defined(IOV_MAX) && IOV_MAX < DEFAULT_WR_IOVCNT
+#define MAX_WR_IOVCNT IOV_MAX
+#else // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
+#define MAX_WR_IOVCNT DEFAULT_WR_IOVCNT
+#endif // !defined(IOV_MAX) || IOV_MAX >= DEFAULT_WR_IOVCNT
+
+inline int limit_iovec(struct iovec *iov, int iovcnt, size_t max) {
+  if (max == 0) {
+    return 0;
+  }
+  for (int i = 0; i < iovcnt; ++i) {
+    auto d = std::min(max, iov[i].iov_len);
+    iov[i].iov_len = d;
+    max -= d;
+    if (max == 0) {
+      return i + 1;
+    }
+  }
+  return iovcnt;
+}
+
+} // namespace nghttp2
+
+#endif // MEMCHUNK_H
diff --git a/src/memchunk_test.cc b/src/memchunk_test.cc
new file mode 100644 (file)
index 0000000..f95f303
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "memchunk_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "memchunk.h"
+#include "util.h"
+
+namespace nghttp2 {
+
+void test_pool_recycle(void) {
+  MemchunkPool pool;
+
+  CU_ASSERT(!pool.pool);
+  CU_ASSERT(0 == pool.poolsize);
+  CU_ASSERT(nullptr == pool.freelist);
+
+  auto m1 = pool.get();
+
+  CU_ASSERT(m1 == pool.pool.get());
+  CU_ASSERT(MemchunkPool::value_type::size == pool.poolsize);
+  CU_ASSERT(nullptr == pool.freelist);
+
+  auto m2 = pool.get();
+
+  CU_ASSERT(m2 == pool.pool.get());
+  CU_ASSERT(2 * MemchunkPool::value_type::size == pool.poolsize);
+  CU_ASSERT(nullptr == pool.freelist);
+  CU_ASSERT(m1 == m2->knext.get());
+  CU_ASSERT(nullptr == m1->knext.get());
+
+  auto m3 = pool.get();
+
+  CU_ASSERT(m3 == pool.pool.get());
+  CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+  CU_ASSERT(nullptr == pool.freelist);
+
+  pool.recycle(m3);
+
+  CU_ASSERT(m3 == pool.pool.get());
+  CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+  CU_ASSERT(m3 == pool.freelist);
+
+  auto m4 = pool.get();
+
+  CU_ASSERT(m3 == m4);
+  CU_ASSERT(m4 == pool.pool.get());
+  CU_ASSERT(3 * MemchunkPool::value_type::size == pool.poolsize);
+  CU_ASSERT(nullptr == pool.freelist);
+
+  pool.recycle(m2);
+  pool.recycle(m1);
+
+  CU_ASSERT(m1 == pool.freelist);
+  CU_ASSERT(m2 == m1->next);
+  CU_ASSERT(nullptr == m2->next);
+}
+
+using Memchunk16 = Memchunk<16>;
+using MemchunkPool16 = Pool<Memchunk16>;
+using Memchunks16 = Memchunks<Memchunk16>;
+
+void test_memchunks_append(void) {
+  MemchunkPool16 pool;
+  Memchunks16 chunks(&pool);
+
+  chunks.append("012");
+
+  auto m = chunks.tail;
+
+  CU_ASSERT(3 == m->len());
+  CU_ASSERT(13 == m->left());
+
+  chunks.append("3456789abcdef@");
+
+  CU_ASSERT(16 == m->len());
+  CU_ASSERT(0 == m->left());
+
+  m = chunks.tail;
+
+  CU_ASSERT(1 == m->len());
+  CU_ASSERT(15 == m->left());
+  CU_ASSERT(17 == chunks.rleft());
+
+  char buf[16];
+  size_t nread;
+
+  nread = chunks.remove(buf, 8);
+
+  CU_ASSERT(8 == nread);
+  CU_ASSERT(0 == memcmp("01234567", buf, nread));
+  CU_ASSERT(9 == chunks.rleft());
+
+  nread = chunks.remove(buf, sizeof(buf));
+
+  CU_ASSERT(9 == nread);
+  CU_ASSERT(0 == memcmp("89abcdef@", buf, nread));
+  CU_ASSERT(0 == chunks.rleft());
+  CU_ASSERT(nullptr == chunks.head);
+  CU_ASSERT(nullptr == chunks.tail);
+  CU_ASSERT(32 == pool.poolsize);
+}
+
+void test_memchunks_drain(void) {
+  MemchunkPool16 pool;
+  Memchunks16 chunks(&pool);
+
+  chunks.append("0123456789");
+
+  size_t nread;
+
+  nread = chunks.drain(3);
+
+  CU_ASSERT(3 == nread);
+
+  char buf[16];
+
+  nread = chunks.remove(buf, sizeof(buf));
+
+  CU_ASSERT(7 == nread);
+  CU_ASSERT(0 == memcmp("3456789", buf, nread));
+}
+
+void test_memchunks_riovec(void) {
+  MemchunkPool16 pool;
+  Memchunks16 chunks(&pool);
+
+  char buf[3 * 16];
+
+  chunks.append(buf, sizeof(buf));
+
+  std::array<struct iovec, 2> iov;
+  auto iovcnt = chunks.riovec(iov.data(), iov.size());
+
+  auto m = chunks.head;
+
+  CU_ASSERT(2 == iovcnt);
+  CU_ASSERT(m->buf.data() == iov[0].iov_base);
+  CU_ASSERT(m->len() == iov[0].iov_len);
+
+  m = m->next;
+
+  CU_ASSERT(m->buf.data() == iov[1].iov_base);
+  CU_ASSERT(m->len() == iov[1].iov_len);
+
+  chunks.drain(2 * 16);
+
+  iovcnt = chunks.riovec(iov.data(), iov.size());
+
+  CU_ASSERT(1 == iovcnt);
+
+  m = chunks.head;
+  CU_ASSERT(m->buf.data() == iov[0].iov_base);
+  CU_ASSERT(m->len() == iov[0].iov_len);
+}
+
+void test_memchunks_recycle(void) {
+  MemchunkPool16 pool;
+  {
+    Memchunks16 chunks(&pool);
+    char buf[32];
+    chunks.append(buf, sizeof(buf));
+  }
+  CU_ASSERT(32 == pool.poolsize);
+  CU_ASSERT(nullptr != pool.freelist);
+
+  auto m = pool.freelist;
+  m = m->next;
+
+  CU_ASSERT(nullptr != m);
+  CU_ASSERT(nullptr == m->next);
+}
+
+} // namespace nghttp2
diff --git a/src/memchunk_test.h b/src/memchunk_test.h
new file mode 100644 (file)
index 0000000..31e2665
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MEMCHUNK_TEST_H
+#define MEMCHUNK_TEST_H
+
+namespace nghttp2 {
+
+void test_pool_recycle(void);
+void test_memchunks_append(void);
+void test_memchunks_drain(void);
+void test_memchunks_riovec(void);
+void test_memchunks_recycle(void);
+
+} // namespace nghttp2
+
+#endif // MEMCHUNK_TEST_H
diff --git a/src/nghttp.cc b/src/nghttp.cc
new file mode 100644 (file)
index 0000000..9415078
--- /dev/null
@@ -0,0 +1,2592 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <getopt.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+#include <tuple>
+
+#include <openssl/err.h>
+#include <openssl/conf.h>
+
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#endif // HAVE_JANSSON
+
+#include "app_helper.h"
+#include "HtmlParser.h"
+#include "util.h"
+#include "base64.h"
+#include "ssl.h"
+#include "template.h"
+
+#ifndef O_BINARY
+#define O_BINARY (0)
+#endif // O_BINARY
+
+namespace nghttp2 {
+
+// stream ID of anchor stream node when --dep-idle is enabled.  These
+// * portion of ANCHOR_ID_* matches RequestPriority in HtmlParser.h.
+// The stream ID = 1 is excluded since it is used as first stream in
+// upgrade case.
+enum {
+  ANCHOR_ID_HIGH = 3,
+  ANCHOR_ID_MEDIUM = 5,
+  ANCHOR_ID_LOW = 7,
+  ANCHOR_ID_LOWEST = 9,
+};
+
+Config::Config()
+    : output_upper_thres(1024 * 1024), padding(0),
+      peer_max_concurrent_streams(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS),
+      header_table_size(-1), weight(NGHTTP2_DEFAULT_WEIGHT), multiply(1),
+      timeout(0.), window_bits(-1), connection_window_bits(-1), verbose(0),
+      null_out(false), remote_name(false), get_assets(false), stat(false),
+      upgrade(false), continuation(false), no_content_length(false),
+      no_dep(false), dep_idle(false) {
+  nghttp2_option_new(&http2_option);
+  nghttp2_option_set_peer_max_concurrent_streams(http2_option,
+                                                 peer_max_concurrent_streams);
+}
+
+Config::~Config() { nghttp2_option_del(http2_option); }
+
+namespace {
+Config config;
+} // namespace
+
+namespace {
+void print_protocol_nego_error() {
+  std::cerr << "[ERROR] HTTP/2 protocol was not selected."
+            << " (nghttp2 expects " << NGHTTP2_PROTO_VERSION_ID << ")"
+            << std::endl;
+}
+} // namespace
+
+namespace {
+std::string strip_fragment(const char *raw_uri) {
+  const char *end;
+  for (end = raw_uri; *end && *end != '#'; ++end)
+    ;
+  size_t len = end - raw_uri;
+  return std::string(raw_uri, len);
+}
+} // namespace
+
+namespace {
+// Returns numeric address string of |addr|.  If getnameinfo() is
+// failed, "unknown" is returned.
+std::string numeric_name(addrinfo *addr) {
+  std::array<char, NI_MAXHOST> host;
+  auto rv = getnameinfo(addr->ai_addr, addr->ai_addrlen, host.data(),
+                        host.size(), nullptr, 0, NI_NUMERICHOST);
+  if (rv != 0) {
+    return "unknown";
+  }
+  return host.data();
+}
+} // namespace
+
+Request::Request(const std::string &uri, const http_parser_url &u,
+                 const nghttp2_data_provider *data_prd, int64_t data_length,
+                 const nghttp2_priority_spec &pri_spec,
+                 std::shared_ptr<Dependency> dep, int pri, int level)
+    : uri(uri), u(u), dep(std::move(dep)), pri_spec(pri_spec),
+      data_length(data_length), data_offset(0), response_len(0),
+      inflater(nullptr), html_parser(nullptr), data_prd(data_prd),
+      stream_id(-1), status(0), level(level), pri(pri),
+      expect_final_response(false) {
+  http2::init_hdidx(res_hdidx);
+  http2::init_hdidx(req_hdidx);
+}
+
+Request::~Request() {
+  nghttp2_gzip_inflate_del(inflater);
+  delete html_parser;
+}
+
+void Request::init_inflater() {
+  int rv;
+  rv = nghttp2_gzip_inflate_new(&inflater);
+  assert(rv == 0);
+}
+
+void Request::init_html_parser() { html_parser = new HtmlParser(uri); }
+
+int Request::update_html_parser(const uint8_t *data, size_t len, int fin) {
+  if (!html_parser) {
+    return 0;
+  }
+  return html_parser->parse_chunk(reinterpret_cast<const char *>(data), len,
+                                  fin);
+}
+
+std::string Request::make_reqpath() const {
+  std::string path = util::has_uri_field(u, UF_PATH)
+                         ? util::get_uri_field(uri.c_str(), u, UF_PATH)
+                         : "/";
+  if (util::has_uri_field(u, UF_QUERY)) {
+    path += "?";
+    path.append(uri.c_str() + u.field_data[UF_QUERY].off,
+                u.field_data[UF_QUERY].len);
+  }
+  return path;
+}
+
+int32_t Request::find_dep_stream_id(int start) {
+  for (auto i = start; i >= 0; --i) {
+    for (auto req : dep->deps[i]) {
+      return req->stream_id;
+    }
+  }
+  return -1;
+}
+
+nghttp2_priority_spec Request::resolve_dep(int32_t pri) {
+  nghttp2_priority_spec pri_spec;
+  int exclusive = 0;
+  int32_t stream_id = -1;
+
+  nghttp2_priority_spec_default_init(&pri_spec);
+
+  if (config.no_dep) {
+    return pri_spec;
+  }
+
+  if (config.dep_idle) {
+    int32_t anchor_id = 0;
+    switch (pri) {
+    case REQ_PRI_HIGH:
+      anchor_id = ANCHOR_ID_HIGH;
+      break;
+    case REQ_PRI_MEDIUM:
+      anchor_id = ANCHOR_ID_MEDIUM;
+      break;
+    case REQ_PRI_LOW:
+      anchor_id = ANCHOR_ID_LOW;
+      break;
+    case REQ_PRI_LOWEST:
+      anchor_id = ANCHOR_ID_LOWEST;
+      break;
+    }
+    nghttp2_priority_spec_init(&pri_spec, anchor_id, NGHTTP2_DEFAULT_WEIGHT, 0);
+    return pri_spec;
+  }
+
+  if (pri == 0) {
+    return pri_spec;
+  }
+
+  auto start = std::min(pri, (int)dep->deps.size() - 1);
+
+  for (auto i = start; i >= 0; --i) {
+    if (dep->deps[i][0]->pri < pri) {
+      stream_id = find_dep_stream_id(i);
+
+      if (i != (int)dep->deps.size() - 1) {
+        exclusive = 1;
+      }
+
+      break;
+    } else if (dep->deps[i][0]->pri == pri) {
+      stream_id = find_dep_stream_id(i - 1);
+
+      break;
+    }
+  }
+
+  if (stream_id == -1) {
+    return pri_spec;
+  }
+
+  nghttp2_priority_spec_init(&pri_spec, stream_id, NGHTTP2_DEFAULT_WEIGHT,
+                             exclusive);
+
+  return pri_spec;
+}
+
+bool Request::is_ipv6_literal_addr() const {
+  if (util::has_uri_field(u, UF_HOST)) {
+    return memchr(uri.c_str() + u.field_data[UF_HOST].off, ':',
+                  u.field_data[UF_HOST].len);
+  } else {
+    return false;
+  }
+}
+
+bool Request::response_pseudo_header_allowed(int16_t token) const {
+  if (!res_nva.empty() && res_nva.back().name.c_str()[0] != ':') {
+    return false;
+  }
+  switch (token) {
+  case http2::HD__STATUS:
+    return res_hdidx[token] == -1;
+  default:
+    return false;
+  }
+}
+
+bool Request::push_request_pseudo_header_allowed(int16_t token) const {
+  if (!req_nva.empty() && req_nva.back().name.c_str()[0] != ':') {
+    return false;
+  }
+  switch (token) {
+  case http2::HD__AUTHORITY:
+  case http2::HD__METHOD:
+  case http2::HD__PATH:
+  case http2::HD__SCHEME:
+    return req_hdidx[token] == -1;
+  default:
+    return false;
+  }
+}
+
+Headers::value_type *Request::get_res_header(int16_t token) {
+  auto idx = res_hdidx[token];
+  if (idx == -1) {
+    return nullptr;
+  }
+  return &res_nva[idx];
+}
+
+Headers::value_type *Request::get_req_header(int16_t token) {
+  auto idx = req_hdidx[token];
+  if (idx == -1) {
+    return nullptr;
+  }
+  return &req_nva[idx];
+}
+
+void Request::record_request_time() {
+  timing.state = RequestState::ON_REQUEST;
+  timing.on_request_time = get_time();
+}
+
+void Request::record_response_time() {
+  timing.state = RequestState::ON_RESPONSE;
+  timing.on_response_time = get_time();
+}
+
+void Request::record_complete_time() {
+  timing.state = RequestState::ON_COMPLETE;
+  timing.on_complete_time = get_time();
+}
+
+namespace {
+int htp_msg_begincb(http_parser *htp) {
+  if (config.verbose) {
+    print_timer();
+    std::cout << " HTTP Upgrade response" << std::endl;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_statuscb(http_parser *htp, const char *at, size_t length) {
+  auto client = static_cast<HttpClient *>(htp->data);
+  client->upgrade_response_status_code = htp->status_code;
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(http_parser *htp) {
+  auto client = static_cast<HttpClient *>(htp->data);
+  client->upgrade_response_complete = true;
+  return 0;
+}
+} // namespace
+
+namespace {
+http_parser_settings htp_hooks = {
+    htp_msg_begincb,   // http_cb      on_message_begin;
+    nullptr,           // http_data_cb on_url;
+    htp_statuscb,      // http_data_cb on_status;
+    nullptr,           // http_data_cb on_header_field;
+    nullptr,           // http_data_cb on_header_value;
+    nullptr,           // http_cb      on_headers_complete;
+    nullptr,           // http_data_cb on_body;
+    htp_msg_completecb // http_cb      on_message_complete;
+};
+} // namespace
+
+namespace {
+int submit_request(HttpClient *client, const Headers &headers, Request *req) {
+  auto path = req->make_reqpath();
+  auto scheme = util::get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
+  auto build_headers = Headers{{":method", req->data_prd ? "POST" : "GET"},
+                               {":path", path},
+                               {":scheme", scheme},
+                               {":authority", client->hostport},
+                               {"accept", "*/*"},
+                               {"accept-encoding", "gzip, deflate"},
+                               {"user-agent", "nghttp2/" NGHTTP2_VERSION}};
+  if (config.continuation) {
+    for (size_t i = 0; i < 6; ++i) {
+      build_headers.emplace_back("continuation-test-" + util::utos(i + 1),
+                                 std::string(4096, '-'));
+    }
+  }
+  auto num_initial_headers = build_headers.size();
+  if (!config.no_content_length && req->data_prd) {
+    build_headers.emplace_back("content-length", util::utos(req->data_length));
+  }
+  for (auto &kv : headers) {
+    size_t i;
+    for (i = 0; i < num_initial_headers; ++i) {
+      if (kv.name == build_headers[i].name) {
+        build_headers[i].value = kv.value;
+        break;
+      }
+    }
+    if (i < num_initial_headers) {
+      continue;
+    }
+
+    build_headers.emplace_back(kv.name, kv.value, kv.no_index);
+  }
+
+  auto nva = std::vector<nghttp2_nv>();
+  nva.reserve(build_headers.size());
+
+  for (auto &kv : build_headers) {
+    nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+  }
+
+  auto stream_id =
+      nghttp2_submit_request(client->session, &req->pri_spec, nva.data(),
+                             nva.size(), req->data_prd, req);
+  if (stream_id < 0) {
+    std::cerr << "[ERROR] nghttp2_submit_request() returned error: "
+              << nghttp2_strerror(stream_id) << std::endl;
+    return -1;
+  }
+
+  req->stream_id = stream_id;
+  client->on_request(req);
+
+  req->req_nva = std::move(build_headers);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto client = static_cast<HttpClient *>(w->data);
+  if (client->do_read() != 0) {
+    client->disconnect();
+  }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto client = static_cast<HttpClient *>(w->data);
+  auto rv = client->do_write();
+  if (rv == HttpClient::ERR_CONNECT_FAIL) {
+    client->on_connect_fail();
+    return;
+  }
+  if (rv != 0) {
+    client->disconnect();
+  }
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto client = static_cast<HttpClient *>(w->data);
+  std::cerr << "[ERROR] Timeout" << std::endl;
+  client->disconnect();
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto client = static_cast<HttpClient *>(w->data);
+  ev_timer_stop(loop, w);
+
+  nghttp2_session_terminate_session(client->session, NGHTTP2_SETTINGS_TIMEOUT);
+
+  client->signal_write();
+}
+} // namespace
+
+HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
+                       struct ev_loop *loop, SSL_CTX *ssl_ctx)
+    : session(nullptr), callbacks(callbacks), loop(loop), ssl_ctx(ssl_ctx),
+      ssl(nullptr), addrs(nullptr), next_addr(nullptr), cur_addr(nullptr),
+      complete(0), settings_payloadlen(0), state(ClientState::IDLE),
+      upgrade_response_status_code(0), fd(-1),
+      upgrade_response_complete(false) {
+  ev_io_init(&wev, writecb, 0, EV_WRITE);
+  ev_io_init(&rev, readcb, 0, EV_READ);
+
+  wev.data = this;
+  rev.data = this;
+
+  ev_timer_init(&wt, timeoutcb, 0., config.timeout);
+  ev_timer_init(&rt, timeoutcb, 0., config.timeout);
+
+  wt.data = this;
+  rt.data = this;
+
+  ev_timer_init(&settings_timer, settings_timeout_cb, 0., 10.);
+
+  settings_timer.data = this;
+}
+
+HttpClient::~HttpClient() {
+  disconnect();
+
+  if (addrs) {
+    freeaddrinfo(addrs);
+    addrs = nullptr;
+    next_addr = nullptr;
+  }
+}
+
+bool HttpClient::need_upgrade() const {
+  return config.upgrade && scheme == "http";
+}
+
+int HttpClient::resolve_host(const std::string &host, uint16_t port) {
+  int rv;
+  addrinfo hints;
+  this->host = host;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_protocol = 0;
+  hints.ai_flags = AI_ADDRCONFIG;
+  rv = getaddrinfo(host.c_str(), util::utos(port).c_str(), &hints, &addrs);
+  if (rv != 0) {
+    std::cerr << "[ERROR] getaddrinfo() failed: " << gai_strerror(rv)
+              << std::endl;
+    return -1;
+  }
+  if (addrs == nullptr) {
+    std::cerr << "[ERROR] No address returned" << std::endl;
+    return -1;
+  }
+  next_addr = addrs;
+  return 0;
+}
+
+int HttpClient::initiate_connection() {
+  int rv;
+
+  cur_addr = nullptr;
+  while (next_addr) {
+    cur_addr = next_addr;
+    next_addr = next_addr->ai_next;
+    fd = util::create_nonblock_socket(cur_addr->ai_family);
+    if (fd == -1) {
+      continue;
+    }
+
+    if (ssl_ctx) {
+      // We are establishing TLS connection.
+      ssl = SSL_new(ssl_ctx);
+      if (!ssl) {
+        std::cerr << "[ERROR] SSL_new() failed: "
+                  << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+        return -1;
+      }
+
+      SSL_set_fd(ssl, fd);
+      SSL_set_connect_state(ssl);
+
+      // If the user overrode the host header, use that value for
+      // the SNI extension
+      const char *host_string = nullptr;
+      auto i =
+          std::find_if(std::begin(config.headers), std::end(config.headers),
+                       [](const Header &nv) { return "host" == nv.name; });
+      if (i != std::end(config.headers)) {
+        host_string = (*i).value.c_str();
+      } else {
+        host_string = host.c_str();
+      }
+
+      if (!util::numeric_host(host_string)) {
+        SSL_set_tlsext_host_name(ssl, host_string);
+      }
+    }
+
+    rv = connect(fd, cur_addr->ai_addr, cur_addr->ai_addrlen);
+
+    if (rv != 0 && errno != EINPROGRESS) {
+      if (ssl) {
+        SSL_free(ssl);
+        ssl = nullptr;
+      }
+      close(fd);
+      fd = -1;
+      continue;
+    }
+    break;
+  }
+
+  if (fd == -1) {
+    return -1;
+  }
+
+  writefn = &HttpClient::connected;
+
+  if (need_upgrade()) {
+    on_readfn = &HttpClient::on_upgrade_read;
+    on_writefn = &HttpClient::on_upgrade_connect;
+  } else {
+    on_readfn = &HttpClient::on_read;
+    on_writefn = &HttpClient::on_write;
+  }
+
+  ev_io_set(&rev, fd, EV_READ);
+  ev_io_set(&wev, fd, EV_WRITE);
+
+  ev_io_start(loop, &wev);
+
+  ev_timer_again(loop, &wt);
+
+  return 0;
+}
+
+void HttpClient::disconnect() {
+  state = ClientState::IDLE;
+
+  ev_timer_stop(loop, &settings_timer);
+
+  ev_timer_stop(loop, &rt);
+  ev_timer_stop(loop, &wt);
+
+  ev_io_stop(loop, &rev);
+  ev_io_stop(loop, &wev);
+
+  nghttp2_session_del(session);
+  session = nullptr;
+
+  if (ssl) {
+    SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
+    ERR_clear_error();
+    SSL_shutdown(ssl);
+    SSL_free(ssl);
+    ssl = nullptr;
+  }
+
+  if (fd != -1) {
+    shutdown(fd, SHUT_WR);
+    close(fd);
+    fd = -1;
+  }
+}
+
+int HttpClient::read_clear() {
+  ev_timer_again(loop, &rt);
+
+  std::array<uint8_t, 8192> buf;
+
+  for (;;) {
+    ssize_t nread;
+    while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR)
+      ;
+    if (nread == -1) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) {
+        return 0;
+      }
+      return -1;
+    }
+
+    if (nread == 0) {
+      return -1;
+    }
+
+    if (on_readfn(*this, buf.data(), nread) != 0) {
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+int HttpClient::write_clear() {
+  ev_timer_again(loop, &rt);
+
+  for (;;) {
+    if (wb.rleft() > 0) {
+      ssize_t nwrite;
+      while ((nwrite = write(fd, wb.pos, wb.rleft())) == -1 && errno == EINTR)
+        ;
+      if (nwrite == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          ev_io_start(loop, &wev);
+          ev_timer_again(loop, &wt);
+          return 0;
+        }
+        return -1;
+      }
+      wb.drain(nwrite);
+      continue;
+    }
+
+    if (on_writefn(*this) != 0) {
+      return -1;
+    }
+    if (wb.rleft() == 0) {
+      wb.reset();
+      break;
+    }
+  }
+
+  ev_io_stop(loop, &wev);
+  ev_timer_stop(loop, &wt);
+
+  return 0;
+}
+
+int HttpClient::noop() { return 0; }
+
+void HttpClient::on_connect_fail() {
+  if (state == ClientState::IDLE) {
+    std::cerr << "[ERROR] Could not connect to the address "
+              << numeric_name(cur_addr) << std::endl;
+  }
+  auto cur_state = state;
+  disconnect();
+  if (cur_state == ClientState::IDLE) {
+    if (initiate_connection() == 0) {
+      std::cerr << "Trying next address " << numeric_name(cur_addr)
+                << std::endl;
+    }
+  }
+}
+
+int HttpClient::connected() {
+  if (!util::check_socket_connected(fd)) {
+    return ERR_CONNECT_FAIL;
+  }
+
+  if (config.verbose) {
+    print_timer();
+    std::cout << " Connected" << std::endl;
+  }
+
+  record_connect_time();
+  state = ClientState::CONNECTED;
+
+  ev_io_start(loop, &rev);
+  ev_io_stop(loop, &wev);
+
+  ev_timer_again(loop, &rt);
+  ev_timer_stop(loop, &wt);
+
+  if (ssl) {
+    readfn = &HttpClient::tls_handshake;
+    writefn = &HttpClient::tls_handshake;
+
+    return do_write();
+  }
+
+  readfn = &HttpClient::read_clear;
+  writefn = &HttpClient::write_clear;
+
+  if (need_upgrade()) {
+    htp = make_unique<http_parser>();
+    http_parser_init(htp.get(), HTTP_RESPONSE);
+    htp->data = this;
+
+    return do_write();
+  }
+
+  if (on_connect() != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+namespace {
+size_t populate_settings(nghttp2_settings_entry *iv) {
+  size_t niv = 2;
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = 100;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  if (config.window_bits != -1) {
+    iv[1].value = (1 << config.window_bits) - 1;
+  } else {
+    iv[1].value = NGHTTP2_INITIAL_WINDOW_SIZE;
+  }
+
+  if (config.header_table_size >= 0) {
+    iv[niv].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+    iv[niv].value = config.header_table_size;
+    ++niv;
+  }
+  return niv;
+}
+} // namespace
+
+int HttpClient::on_upgrade_connect() {
+  ssize_t rv;
+  record_handshake_time();
+  assert(!reqvec.empty());
+  std::array<nghttp2_settings_entry, 32> iv;
+  size_t niv = populate_settings(iv.data());
+  assert(settings_payload.size() >= 8 * niv);
+  rv = nghttp2_pack_settings_payload(settings_payload.data(),
+                                     settings_payload.size(), iv.data(), niv);
+  if (rv < 0) {
+    return -1;
+  }
+  settings_payloadlen = rv;
+  auto token68 =
+      base64::encode(std::begin(settings_payload),
+                     std::begin(settings_payload) + settings_payloadlen);
+  util::to_token68(token68);
+  std::string req;
+  if (reqvec[0]->data_prd) {
+    // If the request contains upload data, use OPTIONS * to upgrade
+    req = "OPTIONS *";
+  } else {
+    req = "GET ";
+    req += reqvec[0]->make_reqpath();
+  }
+  req += " HTTP/1.1\r\n"
+         "Host: ";
+  req += hostport;
+  req += "\r\n"
+         "Connection: Upgrade, HTTP2-Settings\r\n"
+         "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
+         "HTTP2-Settings: ";
+  req += token68;
+  req += "\r\n"
+         "Accept: */*\r\n"
+         "User-Agent: nghttp2/" NGHTTP2_VERSION "\r\n"
+         "\r\n";
+
+  wb.write(req.c_str(), req.size());
+
+  if (config.verbose) {
+    print_timer();
+    std::cout << " HTTP Upgrade request\n" << req << std::endl;
+  }
+
+  // record request time if this is GET request
+  if (!reqvec[0]->data_prd) {
+    reqvec[0]->record_request_time();
+  }
+
+  on_writefn = &HttpClient::noop;
+
+  signal_write();
+
+  return 0;
+}
+
+int HttpClient::on_upgrade_read(const uint8_t *data, size_t len) {
+  int rv;
+
+  auto nread = http_parser_execute(htp.get(), &htp_hooks,
+                                   reinterpret_cast<const char *>(data), len);
+
+  if (config.verbose) {
+    std::cout.write(reinterpret_cast<const char *>(data), nread);
+  }
+
+  auto htperr = HTTP_PARSER_ERRNO(htp.get());
+
+  if (htperr != HPE_OK) {
+    std::cerr << "[ERROR] Failed to parse HTTP Upgrade response header: "
+              << "(" << http_errno_name(htperr) << ") "
+              << http_errno_description(htperr) << std::endl;
+    return -1;
+  }
+
+  if (!upgrade_response_complete) {
+    return 0;
+  }
+
+  if (config.verbose) {
+    std::cout << std::endl;
+  }
+
+  if (upgrade_response_status_code != 101) {
+    std::cerr << "[ERROR] HTTP Upgrade failed" << std::endl;
+
+    return -1;
+  }
+
+  if (config.verbose) {
+    print_timer();
+    std::cout << " HTTP Upgrade success" << std::endl;
+  }
+
+  on_readfn = &HttpClient::on_read;
+  on_writefn = &HttpClient::on_write;
+
+  rv = on_connect();
+  if (rv != 0) {
+    return rv;
+  }
+
+  // Read remaining data in the buffer because it is not notified
+  // callback anymore.
+  rv = on_readfn(*this, data + nread, len - nread);
+  if (rv != 0) {
+    return rv;
+  }
+
+  return 0;
+}
+
+int HttpClient::do_read() { return readfn(*this); }
+int HttpClient::do_write() { return writefn(*this); }
+
+int HttpClient::on_connect() {
+  int rv;
+
+  if (ssl) {
+    // Check NPN or ALPN result
+    const unsigned char *next_proto = nullptr;
+    unsigned int next_proto_len;
+    SSL_get0_next_proto_negotiated(ssl, &next_proto, &next_proto_len);
+    for (int i = 0; i < 2; ++i) {
+      if (next_proto) {
+        if (config.verbose) {
+          std::cout << "The negotiated protocol: ";
+          std::cout.write(reinterpret_cast<const char *>(next_proto),
+                          next_proto_len);
+          std::cout << std::endl;
+        }
+        if (!util::check_h2_is_selected(next_proto, next_proto_len)) {
+          next_proto = nullptr;
+        }
+        break;
+      }
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+      SSL_get0_alpn_selected(ssl, &next_proto, &next_proto_len);
+#else  // OPENSSL_VERSION_NUMBER < 0x10002000L
+      break;
+#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
+    }
+    if (!next_proto) {
+      print_protocol_nego_error();
+      return -1;
+    }
+  }
+
+  if (!need_upgrade()) {
+    record_handshake_time();
+  }
+
+  rv = nghttp2_session_client_new2(&session, callbacks, this,
+                                   config.http2_option);
+
+  if (rv != 0) {
+    return -1;
+  }
+  if (need_upgrade()) {
+    // Adjust stream user-data depending on the existence of upload
+    // data
+    Request *stream_user_data = nullptr;
+    if (!reqvec[0]->data_prd) {
+      stream_user_data = reqvec[0].get();
+    }
+    rv = nghttp2_session_upgrade(session, settings_payload.data(),
+                                 settings_payloadlen, stream_user_data);
+    if (rv != 0) {
+      std::cerr << "[ERROR] nghttp2_session_upgrade() returned error: "
+                << nghttp2_strerror(rv) << std::endl;
+      return -1;
+    }
+    if (stream_user_data) {
+      stream_user_data->stream_id = 1;
+      on_request(stream_user_data);
+    }
+  }
+  // Send connection header here
+  wb.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
+           NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+  // If upgrade succeeds, the SETTINGS value sent with
+  // HTTP2-Settings header field has already been submitted to
+  // session object.
+  if (!need_upgrade()) {
+    std::array<nghttp2_settings_entry, 16> iv;
+    auto niv = populate_settings(iv.data());
+    rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv.data(), niv);
+    if (rv != 0) {
+      return -1;
+    }
+  }
+  if (!config.no_dep && config.dep_idle) {
+    // Create anchor stream nodes
+    nghttp2_priority_spec pri_spec;
+    int32_t dep_stream_id = 0;
+
+    for (auto stream_id :
+         {ANCHOR_ID_HIGH, ANCHOR_ID_MEDIUM, ANCHOR_ID_LOW, ANCHOR_ID_LOWEST}) {
+
+      nghttp2_priority_spec_init(&pri_spec, dep_stream_id,
+                                 NGHTTP2_DEFAULT_WEIGHT, 0);
+      rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, stream_id,
+                                   &pri_spec);
+      if (rv != 0) {
+        return -1;
+      }
+
+      dep_stream_id = stream_id;
+    }
+
+    rv = nghttp2_session_set_next_stream_id(session, ANCHOR_ID_LOWEST + 2);
+    if (rv != 0) {
+      return -1;
+    }
+
+    if (need_upgrade()) {
+      // Amend the priority because we cannot send priority in
+      // HTTP/1.1 Upgrade.
+      nghttp2_priority_spec_init(&pri_spec, ANCHOR_ID_HIGH, config.weight, 0);
+
+      rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
+      if (rv != 0) {
+        return -1;
+      }
+    }
+  } else if (need_upgrade() && config.weight != NGHTTP2_DEFAULT_WEIGHT) {
+    // Amend the priority because we cannot send priority in
+    // HTTP/1.1 Upgrade.
+    nghttp2_priority_spec pri_spec;
+
+    nghttp2_priority_spec_init(&pri_spec, 0, config.weight, 0);
+
+    rv = nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec);
+    if (rv != 0) {
+      return -1;
+    }
+  }
+
+  ev_timer_again(loop, &settings_timer);
+
+  if (config.connection_window_bits != -1) {
+    int32_t wininc = (1 << config.connection_window_bits) - 1 -
+                     NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
+    rv = nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, wininc);
+    if (rv != 0) {
+      return -1;
+    }
+  }
+  // Adjust first request depending on the existence of the upload
+  // data
+  for (auto i = std::begin(reqvec) + (need_upgrade() && !reqvec[0]->data_prd);
+       i != std::end(reqvec); ++i) {
+    if (submit_request(this, config.headers, (*i).get()) != 0) {
+      return -1;
+    }
+  }
+
+  signal_write();
+
+  return 0;
+}
+
+int HttpClient::on_read(const uint8_t *data, size_t len) {
+  auto rv = nghttp2_session_mem_recv(session, data, len);
+  if (rv < 0) {
+    std::cerr << "[ERROR] nghttp2_session_mem_recv() returned error: "
+              << nghttp2_strerror(rv) << std::endl;
+    return -1;
+  }
+
+  assert(static_cast<size_t>(rv) == len);
+
+  if (nghttp2_session_want_read(session) == 0 &&
+      nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
+    return -1;
+  }
+
+  signal_write();
+
+  return 0;
+}
+
+int HttpClient::on_write() {
+  auto rv = nghttp2_session_send(session);
+  if (rv != 0) {
+    std::cerr << "[ERROR] nghttp2_session_send() returned error: "
+              << nghttp2_strerror(rv) << std::endl;
+    return -1;
+  }
+
+  if (nghttp2_session_want_read(session) == 0 &&
+      nghttp2_session_want_write(session) == 0 && wb.rleft() == 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int HttpClient::tls_handshake() {
+  ev_timer_again(loop, &rt);
+
+  ERR_clear_error();
+
+  auto rv = SSL_do_handshake(ssl);
+
+  if (rv == 0) {
+    return -1;
+  }
+
+  if (rv < 0) {
+    auto err = SSL_get_error(ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      ev_io_stop(loop, &wev);
+      ev_timer_stop(loop, &wt);
+      return 0;
+    case SSL_ERROR_WANT_WRITE:
+      ev_io_start(loop, &wev);
+      ev_timer_again(loop, &wt);
+      return 0;
+    default:
+      return -1;
+    }
+  }
+
+  ev_io_stop(loop, &wev);
+  ev_timer_stop(loop, &wt);
+
+  readfn = &HttpClient::read_tls;
+  writefn = &HttpClient::write_tls;
+
+  if (on_connect() != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int HttpClient::read_tls() {
+  ev_timer_again(loop, &rt);
+
+  ERR_clear_error();
+
+  std::array<uint8_t, 8192> buf;
+  for (;;) {
+    auto rv = SSL_read(ssl, buf.data(), buf.size());
+
+    if (rv == 0) {
+      return -1;
+    }
+
+    if (rv < 0) {
+      auto err = SSL_get_error(ssl, rv);
+      switch (err) {
+      case SSL_ERROR_WANT_READ:
+        return 0;
+      case SSL_ERROR_WANT_WRITE:
+        // renegotiation started
+        return -1;
+      default:
+        return -1;
+      }
+    }
+
+    if (on_readfn(*this, buf.data(), rv) != 0) {
+      return -1;
+    }
+  }
+}
+
+int HttpClient::write_tls() {
+  ev_timer_again(loop, &rt);
+
+  ERR_clear_error();
+
+  for (;;) {
+    if (wb.rleft() > 0) {
+      auto rv = SSL_write(ssl, wb.pos, wb.rleft());
+
+      if (rv == 0) {
+        return -1;
+      }
+
+      if (rv < 0) {
+        auto err = SSL_get_error(ssl, rv);
+        switch (err) {
+        case SSL_ERROR_WANT_READ:
+          // renegotiation started
+          return -1;
+        case SSL_ERROR_WANT_WRITE:
+          ev_io_start(loop, &wev);
+          ev_timer_again(loop, &wt);
+          return 0;
+        default:
+          return -1;
+        }
+      }
+
+      wb.drain(rv);
+
+      continue;
+    }
+    if (on_writefn(*this) != 0) {
+      return -1;
+    }
+    if (wb.rleft() == 0) {
+      break;
+    }
+  }
+
+  ev_io_stop(loop, &wev);
+  ev_timer_stop(loop, &wt);
+
+  return 0;
+}
+
+void HttpClient::signal_write() { ev_io_start(loop, &wev); }
+
+bool HttpClient::all_requests_processed() const {
+  return complete == reqvec.size();
+}
+
+void HttpClient::update_hostport() {
+  if (reqvec.empty()) {
+    return;
+  }
+  scheme = util::get_uri_field(reqvec[0]->uri.c_str(), reqvec[0]->u, UF_SCHEMA);
+  std::stringstream ss;
+  if (reqvec[0]->is_ipv6_literal_addr()) {
+    ss << "[";
+    util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
+    ss << "]";
+  } else {
+    util::write_uri_field(ss, reqvec[0]->uri.c_str(), reqvec[0]->u, UF_HOST);
+  }
+  if (util::has_uri_field(reqvec[0]->u, UF_PORT) &&
+      reqvec[0]->u.port !=
+          util::get_default_port(reqvec[0]->uri.c_str(), reqvec[0]->u)) {
+    ss << ":" << reqvec[0]->u.port;
+  }
+  hostport = ss.str();
+}
+
+bool HttpClient::add_request(const std::string &uri,
+                             const nghttp2_data_provider *data_prd,
+                             int64_t data_length,
+                             const nghttp2_priority_spec &pri_spec,
+                             std::shared_ptr<Dependency> dep, int pri,
+                             int level) {
+  http_parser_url u;
+  memset(&u, 0, sizeof(u));
+  if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+    return false;
+  }
+  if (path_cache.count(uri)) {
+    return false;
+  }
+
+  if (config.multiply == 1) {
+    path_cache.insert(uri);
+  }
+
+  reqvec.push_back(make_unique<Request>(uri, u, data_prd, data_length, pri_spec,
+                                        std::move(dep), pri, level));
+  return true;
+}
+
+void HttpClient::record_handshake_time() {
+  timing.on_handshake_time = get_time();
+}
+
+void HttpClient::record_started_time() {
+  timing.started_system_time = std::chrono::system_clock::now();
+  timing.on_started_time = get_time();
+}
+
+void HttpClient::record_dns_complete_time() {
+  timing.on_dns_complete_time = get_time();
+}
+
+void HttpClient::record_connect_time() { timing.on_connect_time = get_time(); }
+
+void HttpClient::on_request(Request *req) {
+  if (req->pri == 0 && req->dep) {
+    assert(req->dep->deps.empty());
+
+    req->dep->deps.push_back(std::vector<Request *>{req});
+
+    return;
+  }
+
+  if (req->stream_id % 2 == 0) {
+    return;
+  }
+
+  auto itr = std::begin(req->dep->deps);
+  for (; itr != std::end(req->dep->deps); ++itr) {
+    if ((*itr)[0]->pri == req->pri) {
+      (*itr).push_back(req);
+
+      break;
+    }
+
+    if ((*itr)[0]->pri > req->pri) {
+      auto v = std::vector<Request *>{req};
+      req->dep->deps.insert(itr, std::move(v));
+
+      break;
+    }
+  }
+
+  if (itr == std::end(req->dep->deps)) {
+    req->dep->deps.push_back(std::vector<Request *>{req});
+  }
+}
+
+#ifdef HAVE_JANSSON
+void HttpClient::output_har(FILE *outfile) {
+  static auto PAGE_ID = "page_0";
+
+  auto root = json_object();
+  auto log = json_object();
+  json_object_set_new(root, "log", log);
+  json_object_set_new(log, "version", json_string("1.2"));
+
+  auto creator = json_object();
+  json_object_set_new(log, "creator", creator);
+
+  json_object_set_new(creator, "name", json_string("nghttp"));
+  json_object_set_new(creator, "version", json_string(NGHTTP2_VERSION));
+
+  auto pages = json_array();
+  json_object_set_new(log, "pages", pages);
+
+  auto page = json_object();
+  json_array_append_new(pages, page);
+
+  json_object_set_new(
+      page, "startedDateTime",
+      json_string(util::format_iso8601(timing.started_system_time).c_str()));
+  json_object_set_new(page, "id", json_string(PAGE_ID));
+  json_object_set_new(page, "title", json_string(""));
+
+  json_object_set_new(page, "pageTimings", json_object());
+
+  auto entries = json_array();
+  json_object_set_new(log, "entries", entries);
+
+  auto dns_delta =
+      std::chrono::duration_cast<std::chrono::microseconds>(
+          timing.on_dns_complete_time - timing.on_started_time).count() /
+      1000.0;
+  auto connect_delta =
+      std::chrono::duration_cast<std::chrono::microseconds>(
+          timing.on_connect_time - timing.on_dns_complete_time).count() /
+      1000.0;
+
+  for (size_t i = 0; i < reqvec.size(); ++i) {
+    auto &req = reqvec[i];
+
+    if (req->timing.state != RequestState::ON_COMPLETE) {
+      continue;
+    }
+
+    auto entry = json_object();
+    json_array_append_new(entries, entry);
+
+    auto &req_timing = req->timing;
+    auto request_time =
+        (i == 0) ? timing.started_system_time
+                 : timing.started_system_time +
+                       std::chrono::duration_cast<
+                           std::chrono::system_clock::duration>(
+                           req_timing.on_request_time - timing.on_started_time);
+
+    auto wait_delta =
+        std::chrono::duration_cast<std::chrono::microseconds>(
+            req_timing.on_response_time - req_timing.on_request_time).count() /
+        1000.0;
+    auto receive_delta =
+        std::chrono::duration_cast<std::chrono::microseconds>(
+            req_timing.on_complete_time - req_timing.on_response_time).count() /
+        1000.0;
+
+    auto time_sum =
+        std::chrono::duration_cast<std::chrono::microseconds>(
+            (i == 0) ? (req_timing.on_complete_time - timing.on_started_time)
+                     : (req_timing.on_complete_time -
+                        req_timing.on_request_time)).count() /
+        1000.0;
+
+    json_object_set_new(
+        entry, "startedDateTime",
+        json_string(util::format_iso8601(request_time).c_str()));
+    json_object_set_new(entry, "time", json_real(time_sum));
+
+    auto request = json_object();
+    json_object_set_new(entry, "request", request);
+
+    auto method_ptr = http2::get_header(req->req_nva, ":method");
+
+    const char *method = "GET";
+    if (method_ptr) {
+      method = (*method_ptr).value.c_str();
+    }
+
+    auto req_headers = json_array();
+    json_object_set_new(request, "headers", req_headers);
+
+    for (auto &nv : req->req_nva) {
+      auto hd = json_object();
+      json_array_append_new(req_headers, hd);
+
+      json_object_set_new(hd, "name", json_string(nv.name.c_str()));
+      json_object_set_new(hd, "value", json_string(nv.value.c_str()));
+    }
+
+    json_object_set_new(request, "method", json_string(method));
+    json_object_set_new(request, "url", json_string(req->uri.c_str()));
+    json_object_set_new(request, "httpVersion", json_string("HTTP/2.0"));
+    json_object_set_new(request, "cookies", json_array());
+    json_object_set_new(request, "queryString", json_array());
+    json_object_set_new(request, "headersSize", json_integer(-1));
+    json_object_set_new(request, "bodySize", json_integer(-1));
+
+    auto response = json_object();
+    json_object_set_new(entry, "response", response);
+
+    auto res_headers = json_array();
+    json_object_set_new(response, "headers", res_headers);
+
+    for (auto &nv : req->res_nva) {
+      auto hd = json_object();
+      json_array_append_new(res_headers, hd);
+
+      json_object_set_new(hd, "name", json_string(nv.name.c_str()));
+      json_object_set_new(hd, "value", json_string(nv.value.c_str()));
+    }
+
+    json_object_set_new(response, "status", json_integer(req->status));
+    json_object_set_new(response, "statusText", json_string(""));
+    json_object_set_new(response, "httpVersion", json_string("HTTP/2.0"));
+    json_object_set_new(response, "cookies", json_array());
+
+    auto content = json_object();
+    json_object_set_new(response, "content", content);
+
+    json_object_set_new(content, "size", json_integer(req->response_len));
+
+    auto content_type_ptr = http2::get_header(req->res_nva, "content-type");
+
+    const char *content_type = "";
+    if (content_type_ptr) {
+      content_type = content_type_ptr->value.c_str();
+    }
+
+    json_object_set_new(content, "mimeType", json_string(content_type));
+
+    json_object_set_new(response, "redirectURL", json_string(""));
+    json_object_set_new(response, "headersSize", json_integer(-1));
+    json_object_set_new(response, "bodySize", json_integer(-1));
+
+    json_object_set_new(entry, "cache", json_object());
+
+    auto timings = json_object();
+    json_object_set_new(entry, "timings", timings);
+
+    auto dns_timing = (i == 0) ? dns_delta : 0;
+    auto connect_timing = (i == 0) ? connect_delta : 0;
+
+    json_object_set_new(timings, "dns", json_real(dns_timing));
+    json_object_set_new(timings, "connect", json_real(connect_timing));
+
+    json_object_set_new(timings, "blocked", json_real(0.0));
+    json_object_set_new(timings, "send", json_real(0.0));
+    json_object_set_new(timings, "wait", json_real(wait_delta));
+    json_object_set_new(timings, "receive", json_real(receive_delta));
+
+    json_object_set_new(entry, "pageref", json_string(PAGE_ID));
+  }
+
+  json_dumpf(root, outfile, JSON_PRESERVE_ORDER | JSON_INDENT(2));
+  json_decref(root);
+}
+#endif // HAVE_JANSSON
+
+namespace {
+void update_html_parser(HttpClient *client, Request *req, const uint8_t *data,
+                        size_t len, int fin) {
+  if (!req->html_parser) {
+    return;
+  }
+  req->update_html_parser(data, len, fin);
+
+  for (auto &p : req->html_parser->get_links()) {
+    auto uri = strip_fragment(p.first.c_str());
+    auto pri = p.second;
+
+    http_parser_url u;
+    memset(&u, 0, sizeof(u));
+    if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
+        util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_SCHEMA) &&
+        util::fieldeq(uri.c_str(), u, req->uri.c_str(), req->u, UF_HOST) &&
+        util::porteq(uri.c_str(), u, req->uri.c_str(), req->u)) {
+      // No POST data for assets
+      auto pri_spec = req->resolve_dep(pri);
+
+      if (client->add_request(uri, nullptr, 0, pri_spec, req->dep, pri,
+                              req->level + 1)) {
+
+        submit_request(client, config.headers, client->reqvec.back().get());
+      }
+    }
+  }
+  req->html_parser->clear_links();
+}
+} // namespace
+
+namespace {
+HttpClient *get_client(void *user_data) {
+  return static_cast<HttpClient *>(user_data);
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) {
+  auto client = get_client(user_data);
+  auto req = static_cast<Request *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+
+  if (!req) {
+    return 0;
+  }
+
+  if (config.verbose >= 2) {
+    verbose_on_data_chunk_recv_callback(session, flags, stream_id, data, len,
+                                        user_data);
+  }
+
+  if (req->status == 0) {
+    nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
+                              NGHTTP2_PROTOCOL_ERROR);
+    return 0;
+  }
+
+  if (req->inflater) {
+    while (len > 0) {
+      const size_t MAX_OUTLEN = 4096;
+      std::array<uint8_t, MAX_OUTLEN> out;
+      size_t outlen = MAX_OUTLEN;
+      size_t tlen = len;
+      int rv =
+          nghttp2_gzip_inflate(req->inflater, out.data(), &outlen, data, &tlen);
+      if (rv != 0) {
+        nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
+                                  NGHTTP2_INTERNAL_ERROR);
+        break;
+      }
+
+      req->response_len += outlen;
+
+      if (!config.null_out) {
+        std::cout.write(reinterpret_cast<const char *>(out.data()), outlen);
+      }
+
+      update_html_parser(client, req, out.data(), outlen, 0);
+      data += tlen;
+      len -= tlen;
+    }
+
+    return 0;
+  }
+
+  req->response_len += len;
+
+  if (!config.null_out) {
+    std::cout.write(reinterpret_cast<const char *>(data), len);
+  }
+
+  update_html_parser(client, req, data, len, 0);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+ssize_t select_padding_callback(nghttp2_session *session,
+                                const nghttp2_frame *frame, size_t max_payload,
+                                void *user_data) {
+  return std::min(max_payload, frame->hd.length + config.padding);
+}
+} // namespace
+
+namespace {
+void check_response_header(nghttp2_session *session, Request *req) {
+  bool gzip = false;
+
+  req->expect_final_response = false;
+
+  auto status_hd = req->get_res_header(http2::HD__STATUS);
+
+  if (!status_hd) {
+    nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
+                              NGHTTP2_PROTOCOL_ERROR);
+    return;
+  }
+
+  auto status = http2::parse_http_status_code(status_hd->value);
+  if (status == -1) {
+    nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, req->stream_id,
+                              NGHTTP2_PROTOCOL_ERROR);
+    return;
+  }
+
+  req->status = status;
+
+  for (auto &nv : req->res_nva) {
+    if ("content-encoding" == nv.name) {
+      gzip =
+          util::strieq("gzip", nv.value) || util::strieq("deflate", nv.value);
+      continue;
+    }
+  }
+
+  if (req->status / 100 == 1) {
+    req->expect_final_response = true;
+    req->status = 0;
+    req->res_nva.clear();
+    http2::init_hdidx(req->res_hdidx);
+    return;
+  }
+
+  if (gzip) {
+    if (!req->inflater) {
+      req->init_inflater();
+    }
+  }
+  if (config.get_assets && req->level == 0) {
+    if (!req->html_parser) {
+      req->init_html_parser();
+    }
+  }
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto client = get_client(user_data);
+  switch (frame->hd.type) {
+  case NGHTTP2_PUSH_PROMISE: {
+    auto stream_id = frame->push_promise.promised_stream_id;
+    http_parser_url u;
+    memset(&u, 0, sizeof(u));
+    // TODO Set pri and level
+    nghttp2_priority_spec pri_spec;
+
+    nghttp2_priority_spec_default_init(&pri_spec);
+
+    auto req = make_unique<Request>("", u, nullptr, 0, pri_spec, nullptr);
+    req->stream_id = stream_id;
+
+    nghttp2_session_set_stream_user_data(session, stream_id, req.get());
+
+    client->on_request(req.get());
+    req->record_request_time();
+    client->reqvec.push_back(std::move(req));
+
+    break;
+  }
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                       const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen, uint8_t flags,
+                       void *user_data) {
+  if (config.verbose) {
+    verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
+                               flags, user_data);
+  }
+
+  if (!http2::check_nv(name, namelen, value, valuelen)) {
+    nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
+                              NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS: {
+    auto req = static_cast<Request *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+
+    if (!req) {
+      break;
+    }
+
+    if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
+        frame->headers.cat != NGHTTP2_HCAT_PUSH_RESPONSE &&
+        (frame->headers.cat != NGHTTP2_HCAT_HEADERS ||
+         !req->expect_final_response)) {
+      break;
+    }
+
+    auto token = http2::lookup_token(name, namelen);
+
+    if (name[0] == ':') {
+      if (!req->response_pseudo_header_allowed(token)) {
+        nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                  frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
+        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+      }
+    }
+
+    http2::index_header(req->res_hdidx, token, req->res_nva.size());
+    http2::add_header(req->res_nva, name, namelen, value, valuelen,
+                      flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+    break;
+  }
+  case NGHTTP2_PUSH_PROMISE: {
+    auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
+        session, frame->push_promise.promised_stream_id));
+
+    if (!req) {
+      break;
+    }
+
+    auto token = http2::lookup_token(name, namelen);
+
+    if (name[0] == ':') {
+      if (!req->push_request_pseudo_header_allowed(token)) {
+        nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                  frame->push_promise.promised_stream_id,
+                                  NGHTTP2_PROTOCOL_ERROR);
+        break;
+      }
+    }
+
+    http2::index_header(req->req_hdidx, token, req->req_nva.size());
+    http2::add_header(req->req_nva, name, namelen, value, valuelen,
+                      flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+    break;
+  }
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback2(nghttp2_session *session,
+                            const nghttp2_frame *frame, void *user_data) {
+  int rv = 0;
+
+  if (config.verbose) {
+    verbose_on_frame_recv_callback(session, frame, user_data);
+  }
+
+  auto client = get_client(user_data);
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS: {
+    auto req = static_cast<Request *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+    // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
+    // req is nullptr.
+    if (!req) {
+      break;
+    }
+
+    if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE ||
+        frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) {
+      req->record_response_time();
+      check_response_header(session, req);
+
+      break;
+    }
+
+    if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+      if (req->expect_final_response) {
+        check_response_header(session, req);
+      } else {
+        nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                  frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
+        break;
+      }
+    }
+
+    if (req->status == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+      nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
+                                NGHTTP2_PROTOCOL_ERROR);
+      break;
+    }
+
+    break;
+  }
+  case NGHTTP2_SETTINGS:
+    if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+      break;
+    }
+    ev_timer_stop(client->loop, &client->settings_timer);
+    break;
+  case NGHTTP2_PUSH_PROMISE: {
+    auto req = static_cast<Request *>(nghttp2_session_get_stream_user_data(
+        session, frame->push_promise.promised_stream_id));
+    if (!req) {
+      break;
+    }
+    auto scheme = req->get_req_header(http2::HD__SCHEME);
+    auto authority = req->get_req_header(http2::HD__AUTHORITY);
+    auto method = req->get_req_header(http2::HD__METHOD);
+    auto path = req->get_req_header(http2::HD__PATH);
+
+    if (!authority) {
+      authority = req->get_req_header(http2::HD_HOST);
+    }
+
+    if (!scheme || !authority || !method || !path || scheme->value.empty() ||
+        authority->value.empty() || method->value.empty() ||
+        path->value.empty() || path->value[0] != '/') {
+      nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                frame->push_promise.promised_stream_id,
+                                NGHTTP2_PROTOCOL_ERROR);
+      break;
+    }
+    std::string uri = scheme->value;
+    uri += "://";
+    uri += authority->value;
+    uri += path->value;
+    http_parser_url u;
+    memset(&u, 0, sizeof(u));
+    if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) {
+      nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                frame->push_promise.promised_stream_id,
+                                NGHTTP2_PROTOCOL_ERROR);
+      break;
+    }
+    req->uri = uri;
+    req->u = u;
+    break;
+  }
+  }
+  return rv;
+}
+} // namespace
+
+namespace {
+int before_frame_send_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, void *user_data) {
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+  auto req = static_cast<Request *>(
+      nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+  req->record_request_time();
+  return 0;
+}
+
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                             uint32_t error_code, void *user_data) {
+  auto client = get_client(user_data);
+  auto req = static_cast<Request *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+
+  if (!req) {
+    return 0;
+  }
+
+  update_html_parser(client, req, nullptr, 0, 1);
+  req->record_complete_time();
+  ++client->complete;
+
+  if (client->all_requests_processed()) {
+    nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR);
+  }
+
+  return 0;
+}
+} // namespace
+
+struct RequestResult {
+  std::chrono::microseconds time;
+};
+
+namespace {
+void print_stats(const HttpClient &client) {
+  std::cout << "***** Statistics *****" << std::endl;
+
+  std::vector<Request *> reqs;
+  reqs.reserve(client.reqvec.size());
+  for (const auto &req : client.reqvec) {
+    if (req->timing.state == RequestState::ON_COMPLETE) {
+      reqs.push_back(req.get());
+    }
+  }
+
+  std::sort(std::begin(reqs), std::end(reqs),
+            [](const Request *lhs, const Request *rhs) {
+    const auto &ltiming = lhs->timing;
+    const auto &rtiming = rhs->timing;
+    return ltiming.on_complete_time < rtiming.on_complete_time ||
+           (ltiming.on_complete_time == rtiming.on_complete_time &&
+            ltiming.on_request_time < rtiming.on_request_time);
+  });
+
+  std::cout << R"(
+Request timing:
+  complete: relative time from protocol handshake to stream close
+   request: relative   time  from   protocol   handshake  to   request
+            transmission
+   process: time for request and response
+      code: HTTP status code
+       URI: request URI
+
+sorted by 'complete'
+
+complete  request   process  code request path)" << std::endl;
+
+  const auto &base = client.timing.on_handshake_time;
+  for (const auto &req : reqs) {
+    auto completed_delta =
+        std::chrono::duration_cast<std::chrono::microseconds>(
+            req->timing.on_complete_time - base);
+    auto request_delta = std::chrono::duration_cast<std::chrono::microseconds>(
+        req->timing.on_request_time - base);
+    auto total = std::chrono::duration_cast<std::chrono::microseconds>(
+        req->timing.on_complete_time - req->timing.on_request_time);
+
+    std::cout << std::setw(9) << ("+" + util::format_duration(completed_delta))
+              << " " << std::setw(9)
+              << ("+" + util::format_duration(request_delta)) << " "
+              << std::setw(8) << util::format_duration(total) << " "
+              << std::setw(4) << req->status << " " << req->make_reqpath()
+              << std::endl;
+  }
+}
+} // namespace
+
+namespace {
+int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
+                                unsigned char *outlen, const unsigned char *in,
+                                unsigned int inlen, void *arg) {
+  if (config.verbose) {
+    print_timer();
+    std::cout << "[NPN] server offers:" << std::endl;
+  }
+  for (unsigned int i = 0; i < inlen; i += in [i] + 1) {
+    if (config.verbose) {
+      std::cout << "          * ";
+      std::cout.write(reinterpret_cast<const char *>(&in[i + 1]), in[i]);
+      std::cout << std::endl;
+    }
+  }
+  if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
+                       inlen)) {
+    print_protocol_nego_error();
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+  return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+namespace {
+// Recommended general purpose "Intermediate compatibility" cipher by
+// mozilla.
+//
+// https://wiki.mozilla.org/Security/Server_Side_TLS
+const char *const CIPHER_LIST =
+    "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-"
+    "AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:"
+    "DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-"
+    "AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-"
+    "AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-"
+    "AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:"
+    "DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-"
+    "SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-"
+    "SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!"
+    "aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA";
+} // namespace
+
+namespace {
+int communicate(
+    const std::string &scheme, const std::string &host, uint16_t port,
+    std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
+        requests, const nghttp2_session_callbacks *callbacks) {
+  int result = 0;
+  auto loop = EV_DEFAULT;
+  SSL_CTX *ssl_ctx = nullptr;
+  if (scheme == "https") {
+    ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+    if (!ssl_ctx) {
+      std::cerr << "[ERROR] Failed to create SSL_CTX: "
+                << ERR_error_string(ERR_get_error(), nullptr) << std::endl;
+      result = -1;
+      goto fin;
+    }
+    SSL_CTX_set_options(ssl_ctx,
+                        SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                            SSL_OP_NO_COMPRESSION |
+                            SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
+    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+    SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+    if (SSL_CTX_set_cipher_list(ssl_ctx, CIPHER_LIST) == 0) {
+      std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
+                << std::endl;
+      result = -1;
+      goto fin;
+    }
+    if (!config.keyfile.empty()) {
+      if (SSL_CTX_use_PrivateKey_file(ssl_ctx, config.keyfile.c_str(),
+                                      SSL_FILETYPE_PEM) != 1) {
+        std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
+                  << std::endl;
+        result = -1;
+        goto fin;
+      }
+    }
+    if (!config.certfile.empty()) {
+      if (SSL_CTX_use_certificate_chain_file(ssl_ctx,
+                                             config.certfile.c_str()) != 1) {
+        std::cerr << "[ERROR] " << ERR_error_string(ERR_get_error(), nullptr)
+                  << std::endl;
+        result = -1;
+        goto fin;
+      }
+    }
+    SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
+                                     nullptr);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+    auto proto_list = util::get_default_alpn();
+
+    SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+  }
+  {
+    HttpClient client{callbacks, loop, ssl_ctx};
+
+    nghttp2_priority_spec pri_spec;
+    int32_t dep_stream_id = 0;
+
+    if (!config.no_dep && config.dep_idle) {
+      dep_stream_id = ANCHOR_ID_HIGH;
+    }
+
+    nghttp2_priority_spec_init(&pri_spec, dep_stream_id, config.weight, 0);
+
+    for (auto req : requests) {
+      for (int i = 0; i < config.multiply; ++i) {
+        auto dep = std::make_shared<Dependency>();
+        client.add_request(std::get<0>(req), std::get<1>(req), std::get<2>(req),
+                           pri_spec, std::move(dep));
+      }
+    }
+    client.update_hostport();
+
+    client.record_started_time();
+
+    if (client.resolve_host(host, port) != 0) {
+      goto fin;
+    }
+
+    client.record_dns_complete_time();
+
+    if (client.initiate_connection() != 0) {
+      goto fin;
+    }
+    ev_run(loop, 0);
+
+#ifdef HAVE_JANSSON
+    if (!config.harfile.empty()) {
+      FILE *outfile;
+      if (config.harfile == "-") {
+        outfile = stdout;
+      } else {
+        outfile = fopen(config.harfile.c_str(), "wb");
+      }
+
+      if (outfile) {
+        client.output_har(outfile);
+
+        if (outfile != stdout) {
+          fclose(outfile);
+        }
+      } else {
+        std::cerr << "Cannot open file " << config.harfile << ". "
+                  << "har file could not be created." << std::endl;
+      }
+    }
+#endif // HAVE_JANSSON
+
+    if (!client.all_requests_processed()) {
+      std::cerr << "Some requests were not processed. total="
+                << client.reqvec.size() << ", processed=" << client.complete
+                << std::endl;
+    }
+    if (config.stat) {
+      print_stats(client);
+    }
+  }
+fin:
+  if (ssl_ctx) {
+    SSL_CTX_free(ssl_ctx);
+  }
+  return result;
+}
+} // namespace
+
+namespace {
+ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
+                           uint8_t *buf, size_t length, uint32_t *data_flags,
+                           nghttp2_data_source *source, void *user_data) {
+  auto req = static_cast<Request *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+  assert(req);
+  int fd = source->fd;
+  ssize_t nread;
+
+  while ((nread = pread(fd, buf, length, req->data_offset)) == -1 &&
+         errno == EINTR)
+    ;
+
+  if (nread == -1) {
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  if (nread == 0) {
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  } else {
+    req->data_offset += nread;
+  }
+
+  return nread;
+}
+} // namespace
+
+namespace {
+ssize_t send_callback(nghttp2_session *session, const uint8_t *data,
+                      size_t length, int flags, void *user_data) {
+  auto client = static_cast<HttpClient *>(user_data);
+  auto &wb = client->wb;
+
+  if (wb.wleft() == 0) {
+    return NGHTTP2_ERR_WOULDBLOCK;
+  }
+
+  return wb.write(data, length);
+}
+} // namespace
+
+namespace {
+int run(char **uris, int n) {
+  nghttp2_session_callbacks *callbacks;
+
+  nghttp2_session_callbacks_new(&callbacks);
+  auto cbsdel = defer(nghttp2_session_callbacks_del, callbacks);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback2);
+
+  if (config.verbose) {
+    nghttp2_session_callbacks_set_on_frame_send_callback(
+        callbacks, verbose_on_frame_send_callback);
+
+    nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
+        callbacks, verbose_on_invalid_frame_recv_callback);
+  }
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_before_frame_send_callback(
+      callbacks, before_frame_send_callback);
+
+  nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
+
+  if (config.padding) {
+    nghttp2_session_callbacks_set_select_padding_callback(
+        callbacks, select_padding_callback);
+  }
+
+  std::string prev_scheme;
+  std::string prev_host;
+  uint16_t prev_port = 0;
+  int failures = 0;
+  int data_fd = -1;
+  nghttp2_data_provider data_prd;
+  struct stat data_stat;
+
+  if (!config.datafile.empty()) {
+    if (config.datafile == "-") {
+      if (fstat(0, &data_stat) == 0 &&
+          (data_stat.st_mode & S_IFMT) == S_IFREG) {
+        // use STDIN if it is a regular file
+        data_fd = 0;
+      } else {
+        // copy the contents of STDIN to a temporary file
+        char tempfn[] = "/tmp/nghttp.temp.XXXXXX";
+        data_fd = mkstemp(tempfn);
+        if (data_fd == -1) {
+          std::cerr << "[ERROR] Could not create a temporary file in /tmp"
+                    << std::endl;
+          return 1;
+        }
+        if (unlink(tempfn) != 0) {
+          std::cerr << "[WARNING] failed to unlink temporary file:" << tempfn
+                    << std::endl;
+        }
+        while (1) {
+          std::array<char, 1024> buf;
+          ssize_t rret, wret;
+          while ((rret = read(0, buf.data(), buf.size())) == -1 &&
+                 errno == EINTR)
+            ;
+          if (rret == 0)
+            break;
+          if (rret == -1) {
+            std::cerr << "[ERROR] I/O error while reading from STDIN"
+                      << std::endl;
+            return 1;
+          }
+          while ((wret = write(data_fd, buf.data(), rret)) == -1 &&
+                 errno == EINTR)
+            ;
+          if (wret != rret) {
+            std::cerr << "[ERROR] I/O error while writing to temporary file"
+                      << std::endl;
+            return 1;
+          }
+        }
+        if (fstat(data_fd, &data_stat) == -1) {
+          close(data_fd);
+          std::cerr << "[ERROR] Could not stat temporary file" << std::endl;
+          return 1;
+        }
+      }
+    } else {
+      data_fd = open(config.datafile.c_str(), O_RDONLY | O_BINARY);
+      if (data_fd == -1) {
+        std::cerr << "[ERROR] Could not open file " << config.datafile
+                  << std::endl;
+        return 1;
+      }
+      if (fstat(data_fd, &data_stat) == -1) {
+        close(data_fd);
+        std::cerr << "[ERROR] Could not stat file " << config.datafile
+                  << std::endl;
+        return 1;
+      }
+    }
+    data_prd.source.fd = data_fd;
+    data_prd.read_callback = file_read_callback;
+  }
+  std::vector<std::tuple<std::string, nghttp2_data_provider *, int64_t>>
+      requests;
+  for (int i = 0; i < n; ++i) {
+    http_parser_url u;
+    memset(&u, 0, sizeof(u));
+    auto uri = strip_fragment(uris[i]);
+    if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) == 0 &&
+        util::has_uri_field(u, UF_SCHEMA)) {
+      uint16_t port = util::has_uri_field(u, UF_PORT)
+                          ? u.port
+                          : util::get_default_port(uri.c_str(), u);
+      if (!util::fieldeq(uri.c_str(), u, UF_SCHEMA, prev_scheme.c_str()) ||
+          !util::fieldeq(uri.c_str(), u, UF_HOST, prev_host.c_str()) ||
+          port != prev_port) {
+        if (!requests.empty()) {
+          if (communicate(prev_scheme, prev_host, prev_port,
+                          std::move(requests), callbacks) != 0) {
+            ++failures;
+          }
+          requests.clear();
+        }
+        prev_scheme = util::get_uri_field(uri.c_str(), u, UF_SCHEMA);
+        prev_host = util::get_uri_field(uri.c_str(), u, UF_HOST);
+        prev_port = port;
+      }
+      requests.emplace_back(uri, data_fd == -1 ? nullptr : &data_prd,
+                            data_stat.st_size);
+    }
+  }
+  if (!requests.empty()) {
+    if (communicate(prev_scheme, prev_host, prev_port, std::move(requests),
+                    callbacks) != 0) {
+      ++failures;
+    }
+  }
+  return failures;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+  out << "nghttp nghttp2/" NGHTTP2_VERSION << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+  out << R"(Usage: nghttp [OPTIONS]... <URI>...
+HTTP/2 experimental client)" << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+  print_usage(out);
+  out << R"(
+  <URI>       Specify URI to access.
+Options:
+  -v, --verbose
+              Print   debug   information   such  as   reception   and
+              transmission of frames and name/value pairs.  Specifying
+              this option multiple times increases verbosity.
+  -n, --null-out
+              Discard downloaded data.
+  -O, --remote-name
+              Save  download  data  in  the  current  directory.   The
+              filename is  dereived from URI.   If URI ends  with '/',
+              'index.html'  is used  as a  filename.  Not  implemented
+              yet.
+  -t, --timeout=<SEC>
+              Timeout each request after <SEC> seconds.
+  -w, --window-bits=<N>
+              Sets the stream level initial window size to 2**<N>-1.
+  -W, --connection-window-bits=<N>
+              Sets  the  connection  level   initial  window  size  to
+              2**<N>-1.
+  -a, --get-assets
+              Download assets  such as stylesheets, images  and script
+              files linked  from the downloaded resource.   Only links
+              whose  origins are  the same  with the  linking resource
+              will be downloaded.   nghttp prioritizes resources using
+              HTTP/2 dependency  based priority.  The  priority order,
+              from highest to lowest,  is html itself, css, javascript
+              and images.
+  -s, --stat  Print statistics.
+  -H, --header=<HEADER>
+              Add a header to the requests.  Example: -H':method: PUT'
+  --cert=<CERT>
+              Use  the specified  client certificate  file.  The  file
+              must be in PEM format.
+  --key=<KEY> Use the  client private key  file.  The file must  be in
+              PEM format.
+  -d, --data=<FILE>
+              Post FILE to server. If '-'  is given, data will be read
+              from stdin.
+  -m, --multiply=<N>
+              Request each URI <N> times.  By default, same URI is not
+              requested twice.  This option disables it too.
+  -u, --upgrade
+              Perform HTTP Upgrade for HTTP/2.  This option is ignored
+              if the request URI has https scheme.  If -d is used, the
+              HTTP upgrade request is performed with OPTIONS method.
+  -p, --weight=<WEIGHT>
+              Sets priority group weight.  The valid value range is
+              [)" << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
+      << R"(], inclusive.
+              Default: )" << NGHTTP2_DEFAULT_WEIGHT << R"(
+  -M, --peer-max-concurrent-streams=<N>
+              Use  <N>  as  SETTINGS_MAX_CONCURRENT_STREAMS  value  of
+              remote endpoint as if it  is received in SETTINGS frame.
+              The default is large enough as it is seen as unlimited.
+  -c, --header-table-size=<SIZE>
+              Specify decoder header table size.
+  -b, --padding=<N>
+              Add at  most <N>  bytes to a  frame payload  as padding.
+              Specify 0 to disable padding.
+  -r, --har=<FILE>
+              Output HTTP  transactions <FILE> in HAR  format.  If '-'
+              is given, data is written to stdout.
+  --color     Force colored log output.
+  --continuation
+              Send large header to test CONTINUATION.
+  --no-content-length
+              Don't send content-length header field.
+  --no-dep    Don't send dependency based priority hint to server.
+  --dep-idle  Use idle streams as anchor nodes to express priority.
+  --version   Display version information and exit.
+  -h, --help  Display this help and exit.
+
+  The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+  10 * 1024).  Units are K, M and G (powers of 1024).)" << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+  bool color = false;
+  while (1) {
+    static int flag = 0;
+    static option long_options[] = {
+        {"verbose", no_argument, nullptr, 'v'},
+        {"null-out", no_argument, nullptr, 'n'},
+        {"remote-name", no_argument, nullptr, 'O'},
+        {"timeout", required_argument, nullptr, 't'},
+        {"window-bits", required_argument, nullptr, 'w'},
+        {"connection-window-bits", required_argument, nullptr, 'W'},
+        {"get-assets", no_argument, nullptr, 'a'},
+        {"stat", no_argument, nullptr, 's'},
+        {"help", no_argument, nullptr, 'h'},
+        {"header", required_argument, nullptr, 'H'},
+        {"data", required_argument, nullptr, 'd'},
+        {"multiply", required_argument, nullptr, 'm'},
+        {"upgrade", no_argument, nullptr, 'u'},
+        {"weight", required_argument, nullptr, 'p'},
+        {"peer-max-concurrent-streams", required_argument, nullptr, 'M'},
+        {"header-table-size", required_argument, nullptr, 'c'},
+        {"padding", required_argument, nullptr, 'b'},
+        {"har", required_argument, nullptr, 'r'},
+        {"cert", required_argument, &flag, 1},
+        {"key", required_argument, &flag, 2},
+        {"color", no_argument, &flag, 3},
+        {"continuation", no_argument, &flag, 4},
+        {"version", no_argument, &flag, 5},
+        {"no-content-length", no_argument, &flag, 6},
+        {"no-dep", no_argument, &flag, 7},
+        {"dep-idle", no_argument, &flag, 8},
+        {nullptr, 0, nullptr, 0}};
+    int option_index = 0;
+    int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
+                        long_options, &option_index);
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+    case 'M':
+      // peer-max-concurrent-streams option
+      config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
+      break;
+    case 'O':
+      config.remote_name = true;
+      break;
+    case 'h':
+      print_help(std::cout);
+      exit(EXIT_SUCCESS);
+    case 'b':
+      config.padding = strtol(optarg, nullptr, 10);
+      break;
+    case 'n':
+      config.null_out = true;
+      break;
+    case 'p': {
+      errno = 0;
+      auto n = strtoul(optarg, nullptr, 10);
+      if (errno == 0 && NGHTTP2_MIN_WEIGHT <= n && n <= NGHTTP2_MAX_WEIGHT) {
+        config.weight = n;
+      } else {
+        std::cerr << "-p: specify the integer in the range ["
+                  << NGHTTP2_MIN_WEIGHT << ", " << NGHTTP2_MAX_WEIGHT
+                  << "], inclusive" << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      break;
+    }
+    case 'r':
+#ifdef HAVE_JANSSON
+      config.harfile = optarg;
+#else  // !HAVE_JANSSON
+      std::cerr << "[WARNING]: -r, --har option is ignored because\n"
+                << "the binary was not compiled with libjansson." << std::endl;
+#endif // !HAVE_JANSSON
+      break;
+    case 'v':
+      ++config.verbose;
+      break;
+    case 't':
+      config.timeout = atoi(optarg);
+      break;
+    case 'u':
+      config.upgrade = true;
+      break;
+    case 'w':
+    case 'W': {
+      errno = 0;
+      char *endptr = nullptr;
+      unsigned long int n = strtoul(optarg, &endptr, 10);
+      if (errno == 0 && *endptr == '\0' && n < 31) {
+        if (c == 'w') {
+          config.window_bits = n;
+        } else {
+          config.connection_window_bits = n;
+        }
+      } else {
+        std::cerr << "-" << static_cast<char>(c)
+                  << ": specify the integer in the range [0, 30], inclusive"
+                  << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      break;
+    }
+    case 'H': {
+      char *header = optarg;
+      // Skip first possible ':' in the header name
+      char *value = strchr(optarg + 1, ':');
+      if (!value || (header[0] == ':' && header + 1 == value)) {
+        std::cerr << "-H: invalid header: " << optarg << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      *value = 0;
+      value++;
+      while (isspace(*value)) {
+        value++;
+      }
+      if (*value == 0) {
+        // This could also be a valid case for suppressing a header
+        // similar to curl
+        std::cerr << "-H: invalid header - value missing: " << optarg
+                  << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      // To test "never index" repr, don't index authorization header
+      // field unconditionally.
+      auto no_index = util::strieq("authorization", header);
+      config.headers.emplace_back(header, value, no_index);
+      util::inp_strlower(config.headers.back().name);
+      break;
+    }
+    case 'a':
+#ifdef HAVE_LIBXML2
+      config.get_assets = true;
+#else  // !HAVE_LIBXML2
+      std::cerr << "[WARNING]: -a, --get-assets option is ignored because\n"
+                << "the binary was not compiled with libxml2." << std::endl;
+#endif // !HAVE_LIBXML2
+      break;
+    case 's':
+      config.stat = true;
+      break;
+    case 'd':
+      config.datafile = optarg;
+      break;
+    case 'm':
+      config.multiply = strtoul(optarg, nullptr, 10);
+      break;
+    case 'c':
+      errno = 0;
+      config.header_table_size = util::parse_uint_with_unit(optarg);
+      if (config.header_table_size == -1) {
+        std::cerr << "-c: Bad option value: " << optarg << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case '?':
+      util::show_candidates(argv[optind - 1], long_options);
+      exit(EXIT_FAILURE);
+    case 0:
+      switch (flag) {
+      case 1:
+        // cert option
+        config.certfile = optarg;
+        break;
+      case 2:
+        // key option
+        config.keyfile = optarg;
+        break;
+      case 3:
+        // color option
+        color = true;
+        break;
+      case 4:
+        // continuation option
+        config.continuation = true;
+        break;
+      case 5:
+        // version option
+        print_version(std::cout);
+        exit(EXIT_SUCCESS);
+      case 6:
+        // no-content-length option
+        config.no_content_length = true;
+        break;
+      case 7:
+        // no-dep option
+        config.no_dep = true;
+        break;
+      case 8:
+        // dep-idle option
+        config.dep_idle = true;
+        break;
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  set_color_output(color || isatty(fileno(stdout)));
+
+  nghttp2_option_set_peer_max_concurrent_streams(
+      config.http2_option, config.peer_max_concurrent_streams);
+
+  struct sigaction act;
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, nullptr);
+  OPENSSL_config(nullptr);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+  reset_timer();
+  return run(argv + optind, argc - optind);
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) { return nghttp2::main(argc, argv); }
diff --git a/src/nghttp.h b/src/nghttp.h
new file mode 100644 (file)
index 0000000..14bbb6b
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP_H
+#define NGHTTP_H
+
+#include "nghttp2_config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <string>
+#include <vector>
+#include <set>
+#include <chrono>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http-parser/http_parser.h"
+
+#include "buffer.h"
+#include "http2.h"
+#include "nghttp2_gzip.h"
+
+namespace nghttp2 {
+
+class HtmlParser;
+
+struct Config {
+  Config();
+  ~Config();
+
+  Headers headers;
+  std::string certfile;
+  std::string keyfile;
+  std::string datafile;
+  std::string harfile;
+  nghttp2_option *http2_option;
+  size_t output_upper_thres;
+  size_t padding;
+  ssize_t peer_max_concurrent_streams;
+  ssize_t header_table_size;
+  int32_t weight;
+  int multiply;
+  // milliseconds
+  ev_tstamp timeout;
+  int window_bits;
+  int connection_window_bits;
+  int verbose;
+  bool null_out;
+  bool remote_name;
+  bool get_assets;
+  bool stat;
+  bool upgrade;
+  bool continuation;
+  bool no_content_length;
+  bool no_dep;
+  bool dep_idle;
+};
+
+enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
+
+struct RequestTiming {
+  std::chrono::steady_clock::time_point on_request_time;
+  std::chrono::steady_clock::time_point on_response_time;
+  std::chrono::steady_clock::time_point on_complete_time;
+  RequestState state;
+  RequestTiming() : state(RequestState::INITIAL) {}
+};
+
+struct Request;
+
+struct Dependency {
+  std::vector<std::vector<Request *>> deps;
+};
+
+struct Request {
+  // For pushed request, |uri| is empty and |u| is zero-cleared.
+  Request(const std::string &uri, const http_parser_url &u,
+          const nghttp2_data_provider *data_prd, int64_t data_length,
+          const nghttp2_priority_spec &pri_spec,
+          std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
+  ~Request();
+
+  void init_inflater();
+
+  void init_html_parser();
+  int update_html_parser(const uint8_t *data, size_t len, int fin);
+
+  std::string make_reqpath() const;
+
+  int32_t find_dep_stream_id(int start);
+
+  nghttp2_priority_spec resolve_dep(int32_t pri);
+
+  bool is_ipv6_literal_addr() const;
+
+  bool response_pseudo_header_allowed(int16_t token) const;
+  bool push_request_pseudo_header_allowed(int16_t token) const;
+
+  Headers::value_type *get_res_header(int16_t token);
+  Headers::value_type *get_req_header(int16_t token);
+
+  void record_request_time();
+  void record_response_time();
+  void record_complete_time();
+
+  Headers res_nva;
+  Headers req_nva;
+  // URI without fragment
+  std::string uri;
+  http_parser_url u;
+  std::shared_ptr<Dependency> dep;
+  nghttp2_priority_spec pri_spec;
+  RequestTiming timing;
+  int64_t data_length;
+  int64_t data_offset;
+  // Number of bytes received from server
+  int64_t response_len;
+  nghttp2_gzip *inflater;
+  HtmlParser *html_parser;
+  const nghttp2_data_provider *data_prd;
+  int32_t stream_id;
+  int status;
+  // Recursion level: 0: first entity, 1: entity linked from first entity
+  int level;
+  // RequestPriority value defined in HtmlParser.h
+  int pri;
+  http2::HeaderIndex res_hdidx;
+  // used for incoming PUSH_PROMISE
+  http2::HeaderIndex req_hdidx;
+  bool expect_final_response;
+};
+
+struct SessionTiming {
+  // The point in time when download was started.
+  std::chrono::system_clock::time_point started_system_time;
+  // The point of time when download was started.
+  std::chrono::steady_clock::time_point on_started_time;
+  // The point of time when DNS resolution was completed.
+  std::chrono::steady_clock::time_point on_dns_complete_time;
+  // The point of time when connection was established or SSL/TLS
+  // handshake was completed.
+  std::chrono::steady_clock::time_point on_connect_time;
+  // The point of time when HTTP/2 commnucation was started.
+  std::chrono::steady_clock::time_point on_handshake_time;
+};
+
+enum class ClientState { IDLE, CONNECTED };
+
+struct HttpClient {
+  HttpClient(const nghttp2_session_callbacks *callbacks, struct ev_loop *loop,
+             SSL_CTX *ssl_ctx);
+  ~HttpClient();
+
+  bool need_upgrade() const;
+  int resolve_host(const std::string &host, uint16_t port);
+  int initiate_connection();
+  void disconnect();
+
+  void on_connect_fail();
+
+  int noop();
+  int read_clear();
+  int write_clear();
+  int connected();
+  int tls_handshake();
+  int read_tls();
+  int write_tls();
+
+  int do_read();
+  int do_write();
+
+  int on_upgrade_connect();
+  int on_upgrade_read(const uint8_t *data, size_t len);
+  int on_read(const uint8_t *data, size_t len);
+  int on_write();
+
+  int on_connect();
+  void on_request(Request *req);
+
+  void signal_write();
+
+  bool all_requests_processed() const;
+  void update_hostport();
+  bool add_request(const std::string &uri,
+                   const nghttp2_data_provider *data_prd, int64_t data_length,
+                   const nghttp2_priority_spec &pri_spec,
+                   std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
+
+  void record_handshake_time();
+  void record_started_time();
+  void record_dns_complete_time();
+  void record_connect_time();
+
+#ifdef HAVE_JANSSON
+  void output_har(FILE *outfile);
+#endif // HAVE_JANSSON
+
+  std::vector<std::unique_ptr<Request>> reqvec;
+  // Insert path already added in reqvec to prevent multiple request
+  // for 1 resource.
+  std::set<std::string> path_cache;
+  std::string scheme;
+  std::string host;
+  std::string hostport;
+  // Used for parse the HTTP upgrade response from server
+  std::unique_ptr<http_parser> htp;
+  SessionTiming timing;
+  ev_io wev;
+  ev_io rev;
+  ev_timer wt;
+  ev_timer rt;
+  ev_timer settings_timer;
+  std::function<int(HttpClient &)> readfn, writefn;
+  std::function<int(HttpClient &, const uint8_t *, size_t)> on_readfn;
+  std::function<int(HttpClient &)> on_writefn;
+  nghttp2_session *session;
+  const nghttp2_session_callbacks *callbacks;
+  struct ev_loop *loop;
+  SSL_CTX *ssl_ctx;
+  SSL *ssl;
+  addrinfo *addrs;
+  addrinfo *next_addr;
+  addrinfo *cur_addr;
+  // The number of completed requests, including failed ones.
+  size_t complete;
+  // The length of settings_payload
+  size_t settings_payloadlen;
+  ClientState state;
+  // The HTTP status code of the response message of HTTP Upgrade.
+  unsigned int upgrade_response_status_code;
+  int fd;
+  // true if the response message of HTTP Upgrade request is fully
+  // received. It is not relevant the upgrade succeeds, or not.
+  bool upgrade_response_complete;
+  Buffer<65536> wb;
+  // SETTINGS payload sent as token68 in HTTP Upgrade
+  std::array<uint8_t, 128> settings_payload;
+
+  enum { ERR_CONNECT_FAIL = -100 };
+};
+
+} // namespace nghttp2
+
+#endif // NGHTTP_H
diff --git a/src/nghttp2_config.h b/src/nghttp2_config.h
new file mode 100644 (file)
index 0000000..9dede35
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_CONFIG_H
+#define NGHTTP2_CONFIG_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+// gcc 4.6 has std::chrono::monotonic_clock, which was renamed as
+// std::chrono::steady_clock in C++11 standard.
+#ifndef HAVE_STEADY_CLOCK
+#define steady_clock monotonic_clock
+#endif // !HAVE_STEADY_CLOCK
+
+#endif // NGHTTP2_CONFIG_H
diff --git a/src/nghttp2_gzip.c b/src/nghttp2_gzip.c
new file mode 100644 (file)
index 0000000..b12f311
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_gzip.h"
+
+#include <assert.h>
+
+int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr) {
+  int rv;
+  *inflater_ptr = malloc(sizeof(nghttp2_gzip));
+  if (*inflater_ptr == NULL) {
+    return -1;
+  }
+  (*inflater_ptr)->finished = 0;
+  (*inflater_ptr)->zst.next_in = Z_NULL;
+  (*inflater_ptr)->zst.avail_in = 0;
+  (*inflater_ptr)->zst.zalloc = Z_NULL;
+  (*inflater_ptr)->zst.zfree = Z_NULL;
+  (*inflater_ptr)->zst.opaque = Z_NULL;
+  rv = inflateInit2(&(*inflater_ptr)->zst, 47);
+  if (rv != Z_OK) {
+    free(*inflater_ptr);
+    return -1;
+  }
+  return 0;
+}
+
+void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater) {
+  if (inflater != NULL) {
+    inflateEnd(&inflater->zst);
+    free(inflater);
+  }
+}
+
+int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out,
+                         size_t *outlen_ptr, const uint8_t *in,
+                         size_t *inlen_ptr) {
+  int rv;
+  if (inflater->finished) {
+    return -1;
+  }
+  inflater->zst.avail_in = (unsigned int)*inlen_ptr;
+  inflater->zst.next_in = (unsigned char *)in;
+  inflater->zst.avail_out = (unsigned int)*outlen_ptr;
+  inflater->zst.next_out = out;
+
+  rv = inflate(&inflater->zst, Z_NO_FLUSH);
+
+  *inlen_ptr -= inflater->zst.avail_in;
+  *outlen_ptr -= inflater->zst.avail_out;
+  switch (rv) {
+  case Z_STREAM_END:
+    inflater->finished = 1;
+  case Z_OK:
+  case Z_BUF_ERROR:
+    return 0;
+  case Z_DATA_ERROR:
+  case Z_STREAM_ERROR:
+  case Z_NEED_DICT:
+  case Z_MEM_ERROR:
+    return -1;
+  default:
+    assert(0);
+    /* We need this for some compilers */
+    return 0;
+  }
+}
+
+int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater) {
+  return inflater->finished;
+}
diff --git a/src/nghttp2_gzip.h b/src/nghttp2_gzip.h
new file mode 100644 (file)
index 0000000..2fa905a
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_GZIP_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+#include <zlib.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @struct
+ *
+ * The gzip stream to inflate data.
+ */
+typedef struct {
+  z_stream zst;
+  int8_t finished;
+} nghttp2_gzip;
+
+/**
+ * @function
+ *
+ * A helper function to set up a per request gzip stream to inflate
+ * data.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ */
+int nghttp2_gzip_inflate_new(nghttp2_gzip **inflater_ptr);
+
+/**
+ * @function
+ *
+ * Frees the inflate stream.  The |inflater| may be ``NULL``.
+ */
+void nghttp2_gzip_inflate_del(nghttp2_gzip *inflater);
+
+/**
+ * @function
+ *
+ * Inflates data in |in| with the length |*inlen_ptr| and stores the
+ * inflated data to |out| which has allocated size at least
+ * |*outlen_ptr|.  On return, |*outlen_ptr| is updated to represent
+ * the number of data written in |out|.  Similarly, |*inlen_ptr| is
+ * updated to represent the number of input bytes processed.
+ *
+ * This function returns 0 if it succeeds, or -1.
+ *
+ * The example follows::
+ *
+ *     void on_data_chunk_recv_callback(nghttp2_session *session,
+ *                                      uint8_t flags,
+ *                                      int32_t stream_id,
+ *                                      const uint8_t *data, size_t len,
+ *                                      void *user_data)
+ *     {
+ *         ...
+ *         req = nghttp2_session_get_stream_user_data(session, stream_id);
+ *         nghttp2_gzip *inflater = req->inflater;
+ *         while(len > 0) {
+ *             uint8_t out[MAX_OUTLEN];
+ *             size_t outlen = MAX_OUTLEN;
+ *             size_t tlen = len;
+ *             int rv;
+ *             rv = nghttp2_gzip_inflate(inflater, out, &outlen, data, &tlen);
+ *             if(rv != 0) {
+ *                 nghttp2_submit_rst_stream(session, stream_id,
+ *                                           NGHTTP2_INTERNAL_ERROR);
+ *                 break;
+ *             }
+ *             ... Do stuff ...
+ *             data += tlen;
+ *             len -= tlen;
+ *         }
+ *         ....
+ *     }
+ */
+int nghttp2_gzip_inflate(nghttp2_gzip *inflater, uint8_t *out,
+                         size_t *outlen_ptr, const uint8_t *in,
+                         size_t *inlen_ptr);
+
+/**
+ * @function
+ *
+ * Returns nonzero if |inflater| sees the end of deflate stream.
+ * After this function returns nonzero, `nghttp2_gzip_inflate()` with
+ * |inflater| gets to return error.
+ */
+int nghttp2_gzip_inflate_finished(nghttp2_gzip *inflater);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NGHTTP2_GZIP_H */
diff --git a/src/nghttp2_gzip_test.c b/src/nghttp2_gzip_test.c
new file mode 100644 (file)
index 0000000..eb43ae6
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_gzip_test.h"
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include <zlib.h>
+
+#include "nghttp2_gzip.h"
+
+static ssize_t deflate_data(uint8_t *out, size_t outlen, const uint8_t *in,
+                            size_t inlen) {
+  int rv;
+  z_stream zst;
+  zst.next_in = Z_NULL;
+  zst.zalloc = Z_NULL;
+  zst.zfree = Z_NULL;
+  zst.opaque = Z_NULL;
+
+  rv = deflateInit(&zst, Z_DEFAULT_COMPRESSION);
+  assert(rv == Z_OK);
+
+  zst.avail_in = (unsigned int)inlen;
+  zst.next_in = (uint8_t *)in;
+  zst.avail_out = (unsigned int)outlen;
+  zst.next_out = out;
+  rv = deflate(&zst, Z_SYNC_FLUSH);
+  assert(rv == Z_OK);
+
+  deflateEnd(&zst);
+
+  return outlen - zst.avail_out;
+}
+
+static const char input[] =
+    "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND "
+    "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
+    "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND "
+    "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE "
+    "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION "
+    "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION "
+    "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
+
+void test_nghttp2_gzip_inflate(void) {
+  nghttp2_gzip *inflater;
+  uint8_t in[4096], out[4096], *inptr;
+  size_t inlen = sizeof(in);
+  size_t inproclen, outproclen;
+  const char *inputptr = input;
+
+  inlen = deflate_data(in, inlen, (const uint8_t *)input, sizeof(input) - 1);
+
+  CU_ASSERT(0 == nghttp2_gzip_inflate_new(&inflater));
+  /* First 16 bytes */
+  inptr = in;
+  inproclen = inlen;
+  outproclen = 16;
+  CU_ASSERT(
+      0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen));
+  CU_ASSERT(16 == outproclen);
+  CU_ASSERT(inproclen > 0);
+  CU_ASSERT(0 == memcmp(inputptr, out, outproclen));
+  /* Next 32 bytes */
+  inptr += inproclen;
+  inlen -= inproclen;
+  inproclen = inlen;
+  inputptr += outproclen;
+  outproclen = 32;
+  CU_ASSERT(
+      0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen));
+  CU_ASSERT(32 == outproclen);
+  CU_ASSERT(inproclen > 0);
+  CU_ASSERT(0 == memcmp(inputptr, out, outproclen));
+  /* Rest */
+  inptr += inproclen;
+  inlen -= inproclen;
+  inproclen = inlen;
+  inputptr += outproclen;
+  outproclen = sizeof(out);
+  CU_ASSERT(
+      0 == nghttp2_gzip_inflate(inflater, out, &outproclen, inptr, &inproclen));
+  CU_ASSERT(sizeof(input) - 49 == outproclen);
+  CU_ASSERT(inproclen > 0);
+  CU_ASSERT(0 == memcmp(inputptr, out, outproclen));
+
+  inlen -= inproclen;
+  CU_ASSERT(0 == inlen);
+
+  nghttp2_gzip_inflate_del(inflater);
+}
diff --git a/src/nghttp2_gzip_test.h b/src/nghttp2_gzip_test.h
new file mode 100644 (file)
index 0000000..ff2598d
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_GZIP_TEST_H
+#define NGHTTP2_GZIP_TEST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void test_nghttp2_gzip_inflate(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NGHTTP2_GZIP_TEST_H */
diff --git a/src/nghttpd.cc b/src/nghttpd.cc
new file mode 100644 (file)
index 0000000..9253593
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_config.h"
+
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <cassert>
+#include <string>
+#include <iostream>
+#include <string>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+#include <nghttp2/nghttp2.h>
+
+#include "app_helper.h"
+#include "HttpServer.h"
+#include "util.h"
+#include "ssl.h"
+
+namespace nghttp2 {
+
+namespace {
+int parse_push_config(Config &config, const char *optarg) {
+  const char *eq = strchr(optarg, '=');
+  if (eq == NULL) {
+    return -1;
+  }
+  auto &paths = config.push[std::string(optarg, eq)];
+  auto optarg_end = optarg + strlen(optarg);
+  auto i = eq + 1;
+  for (;;) {
+    const char *j = strchr(i, ',');
+    if (j == NULL) {
+      j = optarg_end;
+    }
+    paths.emplace_back(i, j);
+    if (j == optarg_end) {
+      break;
+    }
+    i = j;
+    ++i;
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+  out << "nghttpd nghttp2/" NGHTTP2_VERSION << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+  out << "Usage: nghttpd [OPTION]... <PORT> [<PRIVATE_KEY> <CERT>]\n"
+      << "HTTP/2 experimental server" << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+  print_usage(out);
+  out << R"(
+  <PORT>      Specify listening port number.
+  <PRIVATE_KEY>
+              Set  path  to  server's private  key.   Required  unless
+              --no-tls is specified.
+  <CERT>      Set  path  to  server's  certificate.   Required  unless
+              --no-tls is specified.
+Options:
+  -D, --daemon
+              Run in a background.  If -D is used, the current working
+              directory is  changed to '/'.  Therefore  if this option
+              is used, -d option must be specified.
+  -V, --verify-client
+              The server  sends a client certificate  request.  If the
+              client did  not return  a certificate, the  handshake is
+              terminated.   Currently,  this  option just  requests  a
+              client certificate and does not verify it.
+  -d, --htdocs=<PATH>
+              Specify document root.  If this option is not specified,
+              the document root is the current working directory.
+  -v, --verbose
+              Print debug information  such as reception/ transmission
+              of frames and name/value pairs.
+  --no-tls    Disable SSL/TLS.
+  -c, --header-table-size=<SIZE>
+              Specify decoder header table size.
+  --color     Force colored log output.
+  -p, --push=<PATH>=<PUSH_PATH,...>
+              Push  resources <PUSH_PATH>s  when <PATH>  is requested.
+              This option  can be used repeatedly  to specify multiple
+              push  configurations.    <PATH>  and   <PUSH_PATH>s  are
+              relative  to   document  root.   See   --htdocs  option.
+              Example: -p/=/foo.png -p/doc=/bar.css
+  -b, --padding=<N>
+              Add at  most <N>  bytes to a  frame payload  as padding.
+              Specify 0 to disable padding.
+  -n, --workers=<N>
+              Set the number of worker threads.
+              Default: 1
+  -e, --error-gzip
+              Make error response gzipped.
+  --dh-param-file=<PATH>
+              Path to file that contains  DH parameters in PEM format.
+              Without  this   option,  DHE   cipher  suites   are  not
+              available.
+  --early-response
+              Start sending response when request HEADERS is received,
+              rather than complete request is received.
+  --version   Display version information and exit.
+  -h, --help  Display this help and exit.
+
+  The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+  10 * 1024).  Units are K, M and G (powers of 1024).)" << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+  Config config;
+  bool color = false;
+  while (1) {
+    static int flag = 0;
+    static option long_options[] = {
+        {"daemon", no_argument, nullptr, 'D'},
+        {"htdocs", required_argument, nullptr, 'd'},
+        {"help", no_argument, nullptr, 'h'},
+        {"verbose", no_argument, nullptr, 'v'},
+        {"verify-client", no_argument, nullptr, 'V'},
+        {"header-table-size", required_argument, nullptr, 'c'},
+        {"push", required_argument, nullptr, 'p'},
+        {"padding", required_argument, nullptr, 'b'},
+        {"workers", required_argument, nullptr, 'n'},
+        {"error-gzip", no_argument, nullptr, 'e'},
+        {"no-tls", no_argument, &flag, 1},
+        {"color", no_argument, &flag, 2},
+        {"version", no_argument, &flag, 3},
+        {"dh-param-file", required_argument, &flag, 4},
+        {"early-response", no_argument, &flag, 5},
+        {nullptr, 0, nullptr, 0}};
+    int option_index = 0;
+    int c =
+        getopt_long(argc, argv, "DVb:c:d:ehn:p:v", long_options, &option_index);
+    char *end;
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+    case 'D':
+      config.daemon = true;
+      break;
+    case 'V':
+      config.verify_client = true;
+      break;
+    case 'b':
+      config.padding = strtol(optarg, nullptr, 10);
+      break;
+    case 'd':
+      config.htdocs = optarg;
+      break;
+    case 'e':
+      config.error_gzip = true;
+      break;
+    case 'n':
+#ifdef NOTHREADS
+      std::cerr << "-n: WARNING: Threading disabled at build time, "
+                << "no threads created." << std::endl;
+#else
+      errno = 0;
+      config.num_worker = strtoul(optarg, &end, 10);
+      if (errno == ERANGE || *end != '\0' || config.num_worker == 0) {
+        std::cerr << "-n: Bad option value: " << optarg << std::endl;
+        exit(EXIT_FAILURE);
+      }
+#endif // NOTHREADS
+      break;
+    case 'h':
+      print_help(std::cout);
+      exit(EXIT_SUCCESS);
+    case 'v':
+      config.verbose = true;
+      break;
+    case 'c':
+      errno = 0;
+      config.header_table_size = util::parse_uint_with_unit(optarg);
+      if (config.header_table_size == -1) {
+        std::cerr << "-c: Bad option value: " << optarg << std::endl;
+        exit(EXIT_FAILURE);
+      }
+      break;
+    case 'p':
+      if (parse_push_config(config, optarg) != 0) {
+        std::cerr << "-p: Bad option value: " << optarg << std::endl;
+      }
+      break;
+    case '?':
+      util::show_candidates(argv[optind - 1], long_options);
+      exit(EXIT_FAILURE);
+    case 0:
+      switch (flag) {
+      case 1:
+        // no-tls option
+        config.no_tls = true;
+        break;
+      case 2:
+        // color option
+        color = true;
+        break;
+      case 3:
+        // version
+        print_version(std::cout);
+        exit(EXIT_SUCCESS);
+      case 4:
+        // dh-param-file
+        config.dh_param_file = optarg;
+        break;
+      case 5:
+        // early-response
+        config.early_response = true;
+        break;
+      }
+      break;
+    default:
+      break;
+    }
+  }
+  if (argc - optind < (config.no_tls ? 1 : 3)) {
+    print_usage(std::cerr);
+    std::cerr << "Too few arguments" << std::endl;
+    exit(EXIT_FAILURE);
+  }
+
+  config.port = strtol(argv[optind++], nullptr, 10);
+
+  if (!config.no_tls) {
+    config.private_key_file = argv[optind++];
+    config.cert_file = argv[optind++];
+  }
+
+  if (config.daemon) {
+    if (config.htdocs.empty()) {
+      print_usage(std::cerr);
+      std::cerr << "-d option must be specified when -D is used." << std::endl;
+      exit(EXIT_FAILURE);
+    }
+    if (daemon(0, 0) == -1) {
+      perror("daemon");
+      exit(EXIT_FAILURE);
+    }
+  }
+  if (config.htdocs.empty()) {
+    config.htdocs = "./";
+  }
+
+  set_color_output(color || isatty(fileno(stdout)));
+
+  struct sigaction act;
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, nullptr);
+  OPENSSL_config(nullptr);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+#ifndef NOTHREADS
+  ssl::LibsslGlobalLock lock;
+#endif // NOTHREADS
+
+  reset_timer();
+
+  HttpServer server(&config);
+  server.run();
+  return 0;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) { return nghttp2::main(argc, argv); }
diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc
new file mode 100644 (file)
index 0000000..7e3e901
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <string.h>
+#include <CUnit/Basic.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+// include test cases' include files here
+#include "shrpx_ssl_test.h"
+#include "shrpx_downstream_test.h"
+#include "shrpx_config_test.h"
+#include "http2_test.h"
+#include "util_test.h"
+#include "nghttp2_gzip_test.h"
+#include "buffer_test.h"
+#include "memchunk_test.h"
+#include "shrpx_config.h"
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(int argc, char *argv[]) {
+  CU_pSuite pSuite = NULL;
+  unsigned int num_tests_failed;
+
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  shrpx::create_config();
+
+  // initialize the CUnit test registry
+  if (CUE_SUCCESS != CU_initialize_registry())
+    return CU_get_error();
+
+  // add a suite to the registry
+  pSuite = CU_add_suite("shrpx_TestSuite", init_suite1, clean_suite1);
+  if (NULL == pSuite) {
+    CU_cleanup_registry();
+    return CU_get_error();
+  }
+
+  // add the tests to the suite
+  if (!CU_add_test(pSuite, "ssl_create_lookup_tree",
+                   shrpx::test_shrpx_ssl_create_lookup_tree) ||
+      !CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
+                   shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
+      !CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
+      !CU_add_test(pSuite, "http2_get_header", shrpx::test_http2_get_header) ||
+      !CU_add_test(pSuite, "http2_copy_headers_to_nva",
+                   shrpx::test_http2_copy_headers_to_nva) ||
+      !CU_add_test(pSuite, "http2_build_http1_headers_from_headers",
+                   shrpx::test_http2_build_http1_headers_from_headers) ||
+      !CU_add_test(pSuite, "http2_lws", shrpx::test_http2_lws) ||
+      !CU_add_test(pSuite, "http2_rewrite_location_uri",
+                   shrpx::test_http2_rewrite_location_uri) ||
+      !CU_add_test(pSuite, "http2_parse_http_status_code",
+                   shrpx::test_http2_parse_http_status_code) ||
+      !CU_add_test(pSuite, "http2_index_header",
+                   shrpx::test_http2_index_header) ||
+      !CU_add_test(pSuite, "http2_lookup_token",
+                   shrpx::test_http2_lookup_token) ||
+      !CU_add_test(pSuite, "http2_check_http2_pseudo_header",
+                   shrpx::test_http2_check_http2_pseudo_header) ||
+      !CU_add_test(pSuite, "http2_http2_header_allowed",
+                   shrpx::test_http2_http2_header_allowed) ||
+      !CU_add_test(pSuite, "http2_mandatory_request_headers_presence",
+                   shrpx::test_http2_mandatory_request_headers_presence) ||
+      !CU_add_test(pSuite, "http2_parse_link_header",
+                   shrpx::test_http2_parse_link_header) ||
+      !CU_add_test(pSuite, "http2_path_join", shrpx::test_http2_path_join) ||
+      !CU_add_test(pSuite, "downstream_index_request_headers",
+                   shrpx::test_downstream_index_request_headers) ||
+      !CU_add_test(pSuite, "downstream_index_response_headers",
+                   shrpx::test_downstream_index_response_headers) ||
+      !CU_add_test(pSuite, "downstream_get_request_header",
+                   shrpx::test_downstream_get_request_header) ||
+      !CU_add_test(pSuite, "downstream_get_response_header",
+                   shrpx::test_downstream_get_response_header) ||
+      !CU_add_test(pSuite, "downstream_crumble_request_cookie",
+                   shrpx::test_downstream_crumble_request_cookie) ||
+      !CU_add_test(pSuite, "downstream_assemble_request_cookie",
+                   shrpx::test_downstream_assemble_request_cookie) ||
+      !CU_add_test(pSuite, "downstream_rewrite_location_response_header",
+                   shrpx::test_downstream_rewrite_location_response_header) ||
+      !CU_add_test(pSuite, "config_parse_config_str_list",
+                   shrpx::test_shrpx_config_parse_config_str_list) ||
+      !CU_add_test(pSuite, "config_parse_header",
+                   shrpx::test_shrpx_config_parse_header) ||
+      !CU_add_test(pSuite, "config_parse_log_format",
+                   shrpx::test_shrpx_config_parse_log_format) ||
+      !CU_add_test(pSuite, "config_read_tls_ticket_key_file",
+                   shrpx::test_shrpx_config_read_tls_ticket_key_file) ||
+      !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
+      !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
+      !CU_add_test(pSuite, "util_inp_strlower",
+                   shrpx::test_util_inp_strlower) ||
+      !CU_add_test(pSuite, "util_to_base64", shrpx::test_util_to_base64) ||
+      !CU_add_test(pSuite, "util_percent_encode_token",
+                   shrpx::test_util_percent_encode_token) ||
+      !CU_add_test(pSuite, "util_quote_string",
+                   shrpx::test_util_quote_string) ||
+      !CU_add_test(pSuite, "util_utox", shrpx::test_util_utox) ||
+      !CU_add_test(pSuite, "util_http_date", shrpx::test_util_http_date) ||
+      !CU_add_test(pSuite, "util_select_h2", shrpx::test_util_select_h2) ||
+      !CU_add_test(pSuite, "util_ipv6_numeric_addr",
+                   shrpx::test_util_ipv6_numeric_addr) ||
+      !CU_add_test(pSuite, "util_utos_with_unit",
+                   shrpx::test_util_utos_with_unit) ||
+      !CU_add_test(pSuite, "util_utos_with_funit",
+                   shrpx::test_util_utos_with_funit) ||
+      !CU_add_test(pSuite, "util_parse_uint_with_unit",
+                   shrpx::test_util_parse_uint_with_unit) ||
+      !CU_add_test(pSuite, "util_parse_uint", shrpx::test_util_parse_uint) ||
+      !CU_add_test(pSuite, "util_parse_duration_with_unit",
+                   shrpx::test_util_parse_duration_with_unit) ||
+      !CU_add_test(pSuite, "util_duration_str",
+                   shrpx::test_util_duration_str) ||
+      !CU_add_test(pSuite, "util_format_duration",
+                   shrpx::test_util_format_duration) ||
+      !CU_add_test(pSuite, "gzip_inflate", test_nghttp2_gzip_inflate) ||
+      !CU_add_test(pSuite, "buffer_write", nghttp2::test_buffer_write) ||
+      !CU_add_test(pSuite, "pool_recycle", nghttp2::test_pool_recycle) ||
+      !CU_add_test(pSuite, "memchunk_append", nghttp2::test_memchunks_append) ||
+      !CU_add_test(pSuite, "memchunk_drain", nghttp2::test_memchunks_drain) ||
+      !CU_add_test(pSuite, "memchunk_riovec", nghttp2::test_memchunks_riovec) ||
+      !CU_add_test(pSuite, "memchunk_recycle",
+                   nghttp2::test_memchunks_recycle)) {
+    CU_cleanup_registry();
+    return CU_get_error();
+  }
+
+  // Run all tests using the CUnit Basic interface
+  CU_basic_set_mode(CU_BRM_VERBOSE);
+  CU_basic_run_tests();
+  num_tests_failed = CU_get_number_of_tests_failed();
+  CU_cleanup_registry();
+  if (CU_get_error() == CUE_SUCCESS) {
+    return num_tests_failed;
+  } else {
+    printf("CUnit Error: %s\n", CU_get_error_msg());
+    return CU_get_error();
+  }
+}
diff --git a/src/shrpx.cc b/src/shrpx.cc
new file mode 100644 (file)
index 0000000..689c9ad
--- /dev/null
@@ -0,0 +1,1954 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <signal.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <syslog.h>
+#include <signal.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <grp.h>
+
+#include <limits>
+#include <cstdlib>
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/conf.h>
+#include <openssl/rand.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_config.h"
+#include "shrpx_connection_handler.h"
+#include "shrpx_ssl.h"
+#include "shrpx_worker_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_accept_handler.h"
+#include "util.h"
+#include "app_helper.h"
+#include "ssl.h"
+#include "template.h"
+
+extern char **environ;
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+const int REOPEN_LOG_SIGNAL = SIGUSR1;
+const int EXEC_BINARY_SIGNAL = SIGUSR2;
+const int GRACEFUL_SHUTDOWN_SIGNAL = SIGQUIT;
+} // namespace
+
+// Environment variables to tell new binary the listening socket's
+// file descriptors.  They are not close-on-exec.
+#define ENV_LISTENER4_FD "NGHTTPX_LISTENER4_FD"
+#define ENV_LISTENER6_FD "NGHTTPX_LISTENER6_FD"
+
+// Environment variable to tell new binary the port number the current
+// binary is listening to.
+#define ENV_PORT "NGHTTPX_PORT"
+
+namespace {
+int resolve_hostname(sockaddr_union *addr, size_t *addrlen,
+                     const char *hostname, uint16_t port, int family) {
+  addrinfo hints;
+  int rv;
+
+  auto service = util::utos(port);
+  memset(&hints, 0, sizeof(addrinfo));
+
+  hints.ai_family = family;
+  hints.ai_socktype = SOCK_STREAM;
+#ifdef AI_ADDRCONFIG
+  hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+  addrinfo *res;
+
+  rv = getaddrinfo(hostname, service.c_str(), &hints, &res);
+  if (rv != 0) {
+    LOG(FATAL) << "Unable to resolve address for " << hostname << ": "
+               << gai_strerror(rv);
+    return -1;
+  }
+
+  char host[NI_MAXHOST];
+  rv = getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), 0, 0,
+                   NI_NUMERICHOST);
+  if (rv != 0) {
+    LOG(FATAL) << "Address resolution for " << hostname
+               << " failed: " << gai_strerror(rv);
+
+    freeaddrinfo(res);
+
+    return -1;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "Address resolution for " << hostname
+              << " succeeded: " << host;
+  }
+
+  memcpy(addr, res->ai_addr, res->ai_addrlen);
+  *addrlen = res->ai_addrlen;
+  freeaddrinfo(res);
+  return 0;
+}
+} // namespace
+
+namespace {
+std::unique_ptr<AcceptHandler> create_acceptor(ConnectionHandler *handler,
+                                               int family) {
+  {
+    auto envfd =
+        getenv(family == AF_INET ? ENV_LISTENER4_FD : ENV_LISTENER6_FD);
+    auto envport = getenv(ENV_PORT);
+
+    if (envfd && envport) {
+      auto fd = strtoul(envfd, nullptr, 10);
+      auto port = strtoul(envport, nullptr, 10);
+
+      // Only do this iff NGHTTPX_PORT == get_config()->port.
+      // Otherwise, close fd, and create server socket as usual.
+
+      if (port == get_config()->port) {
+        LOG(NOTICE) << "Listening on port " << get_config()->port;
+
+        return make_unique<AcceptHandler>(fd, handler);
+      }
+
+      LOG(WARN) << "Port was changed between old binary (" << port
+                << ") and new binary (" << get_config()->port << ")";
+      close(fd);
+    }
+  }
+
+  addrinfo hints;
+  int fd = -1;
+  int rv;
+
+  auto service = util::utos(get_config()->port);
+  memset(&hints, 0, sizeof(addrinfo));
+  hints.ai_family = family;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_flags = AI_PASSIVE;
+#ifdef AI_ADDRCONFIG
+  hints.ai_flags |= AI_ADDRCONFIG;
+#endif // AI_ADDRCONFIG
+
+  auto node = strcmp("*", get_config()->host.get()) == 0
+                  ? nullptr
+                  : get_config()->host.get();
+
+  addrinfo *res, *rp;
+  rv = getaddrinfo(node, service.c_str(), &hints, &res);
+  if (rv != 0) {
+    if (LOG_ENABLED(INFO)) {
+      LOG(INFO) << "Unable to get IPv" << (family == AF_INET ? "4" : "6")
+                << " address for " << get_config()->host.get() << ": "
+                << gai_strerror(rv);
+    }
+    return nullptr;
+  }
+  for (rp = res; rp; rp = rp->ai_next) {
+#ifdef SOCK_NONBLOCK
+    fd =
+        socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol);
+    if (fd == -1) {
+      continue;
+    }
+#else  // !SOCK_NONBLOCK
+    fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+    if (fd == -1) {
+      continue;
+    }
+    util::make_socket_nonblocking(fd);
+#endif // !SOCK_NONBLOCK
+    int val = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val,
+                   static_cast<socklen_t>(sizeof(val))) == -1) {
+      close(fd);
+      continue;
+    }
+
+#ifdef IPV6_V6ONLY
+    if (family == AF_INET6) {
+      if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+                     static_cast<socklen_t>(sizeof(val))) == -1) {
+        close(fd);
+        continue;
+      }
+    }
+#endif // IPV6_V6ONLY
+
+#ifdef TCP_DEFER_ACCEPT
+    val = 3;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val,
+                   static_cast<socklen_t>(sizeof(val))) == -1) {
+      LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket";
+    }
+#endif // TCP_DEFER_ACCEPT
+
+    if (bind(fd, rp->ai_addr, rp->ai_addrlen) == 0 &&
+        listen(fd, get_config()->backlog) == 0) {
+      break;
+    }
+    close(fd);
+  }
+
+  if (!rp) {
+    LOG(WARN) << "Listening " << (family == AF_INET ? "IPv4" : "IPv6")
+              << " socket failed";
+
+    freeaddrinfo(res);
+
+    return nullptr;
+  }
+
+  char host[NI_MAXHOST];
+  rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), nullptr, 0,
+                   NI_NUMERICHOST);
+
+  freeaddrinfo(res);
+
+  if (rv != 0) {
+    LOG(WARN) << gai_strerror(rv);
+
+    close(fd);
+
+    return nullptr;
+  }
+
+  LOG(NOTICE) << "Listening on " << host << ", port " << get_config()->port;
+
+  return make_unique<AcceptHandler>(fd, handler);
+}
+} // namespace
+
+namespace {
+void drop_privileges() {
+  if (getuid() == 0 && get_config()->uid != 0) {
+    if (initgroups(get_config()->user.get(), get_config()->gid) != 0) {
+      auto error = errno;
+      LOG(FATAL) << "Could not change supplementary groups: "
+                 << strerror(error);
+      exit(EXIT_FAILURE);
+    }
+    if (setgid(get_config()->gid) != 0) {
+      auto error = errno;
+      LOG(FATAL) << "Could not change gid: " << strerror(error);
+      exit(EXIT_FAILURE);
+    }
+    if (setuid(get_config()->uid) != 0) {
+      auto error = errno;
+      LOG(FATAL) << "Could not change uid: " << strerror(error);
+      exit(EXIT_FAILURE);
+    }
+    if (setuid(0) != -1) {
+      LOG(FATAL) << "Still have root privileges?";
+      exit(EXIT_FAILURE);
+    }
+  }
+}
+} // namespace
+
+namespace {
+void save_pid() {
+  std::ofstream out(get_config()->pid_file.get(), std::ios::binary);
+  out << get_config()->pid << "\n";
+  out.close();
+  if (!out) {
+    LOG(ERROR) << "Could not save PID to file " << get_config()->pid_file.get();
+    exit(EXIT_FAILURE);
+  }
+
+  if (get_config()->uid != 0) {
+    if (chown(get_config()->pid_file.get(), get_config()->uid,
+              get_config()->gid) == -1) {
+      auto error = errno;
+      LOG(WARN) << "Changing owner of pid file " << get_config()->pid_file.get()
+                << " failed: " << strerror(error);
+    }
+  }
+}
+} // namespace
+
+namespace {
+void reopen_log_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
+  auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "Reopening log files: worker_info(" << worker_config << ")";
+  }
+
+  (void)reopen_log_files();
+
+  if (get_config()->num_worker > 1) {
+    conn_handler->worker_reopen_log_files();
+  }
+}
+} // namespace
+
+namespace {
+void exec_binary_signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
+  auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+
+  LOG(NOTICE) << "Executing new binary";
+
+  auto pid = fork();
+
+  if (pid == -1) {
+    auto error = errno;
+    LOG(ERROR) << "fork() failed errno=" << error;
+    return;
+  }
+
+  if (pid != 0) {
+    return;
+  }
+
+  auto exec_path = util::get_exec_path(get_config()->argc, get_config()->argv,
+                                       get_config()->cwd);
+
+  if (!exec_path) {
+    LOG(ERROR) << "Could not resolve the executable path";
+    return;
+  }
+
+  auto argv = make_unique<char *[]>(get_config()->argc + 1);
+
+  argv[0] = exec_path;
+  for (int i = 1; i < get_config()->argc; ++i) {
+    argv[i] = strdup(get_config()->argv[i]);
+  }
+  argv[get_config()->argc] = nullptr;
+
+  size_t envlen = 0;
+  for (char **p = environ; *p; ++p, ++envlen)
+    ;
+  // 3 for missing fd4, fd6 and port.
+  auto envp = make_unique<char *[]>(envlen + 3 + 1);
+  size_t envidx = 0;
+
+  auto acceptor4 = conn_handler->get_acceptor4();
+  if (acceptor4) {
+    std::string fd4 = ENV_LISTENER4_FD "=";
+    fd4 += util::utos(acceptor4->get_fd());
+    envp[envidx++] = strdup(fd4.c_str());
+  }
+
+  auto acceptor6 = conn_handler->get_acceptor6();
+  if (acceptor6) {
+    std::string fd6 = ENV_LISTENER6_FD "=";
+    fd6 += util::utos(acceptor6->get_fd());
+    envp[envidx++] = strdup(fd6.c_str());
+  }
+
+  std::string port = ENV_PORT "=";
+  port += util::utos(get_config()->port);
+  envp[envidx++] = strdup(port.c_str());
+
+  for (size_t i = 0; i < envlen; ++i) {
+    if (strcmp(ENV_LISTENER4_FD, environ[i]) == 0 ||
+        strcmp(ENV_LISTENER6_FD, environ[i]) == 0 ||
+        strcmp(ENV_PORT, environ[i]) == 0) {
+      continue;
+    }
+
+    envp[envidx++] = environ[i];
+  }
+
+  envp[envidx++] = nullptr;
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "cmdline";
+    for (int i = 0; argv[i]; ++i) {
+      LOG(INFO) << i << ": " << argv[i];
+    }
+    LOG(INFO) << "environ";
+    for (int i = 0; envp[i]; ++i) {
+      LOG(INFO) << i << ": " << envp[i];
+    }
+  }
+
+  if (execve(argv[0], argv.get(), envp.get()) == -1) {
+    auto error = errno;
+    LOG(ERROR) << "execve failed: errno=" << error;
+    _Exit(EXIT_FAILURE);
+  }
+}
+} // namespace
+
+namespace {
+void graceful_shutdown_signal_cb(struct ev_loop *loop, ev_signal *w,
+                                 int revents) {
+  auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+
+  if (worker_config->graceful_shutdown) {
+    return;
+  }
+
+  LOG(NOTICE) << "Graceful shutdown signal received";
+
+  worker_config->graceful_shutdown = true;
+
+  conn_handler->disable_acceptor();
+
+  // After disabling accepting new connection, disptach incoming
+  // connection in backlog.
+
+  conn_handler->accept_pending_connection();
+
+  conn_handler->graceful_shutdown_worker();
+
+  if (get_config()->num_worker == 1) {
+    return;
+  }
+
+  // We have accepted all pending connections.  Shutdown main event
+  // loop.
+  ev_break(loop);
+}
+} // namespace
+
+namespace {
+void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+  auto worker_stat = conn_handler->get_worker_stat();
+
+  // In multi threaded mode (get_config()->num_worker > 1), we have to
+  // wait for event notification to workers to finish.
+  if (get_config()->num_worker == 1 && worker_config->graceful_shutdown &&
+      (!worker_stat || worker_stat->num_connections == 0)) {
+    ev_break(loop);
+  }
+}
+} // namespace
+
+namespace {
+void renew_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto conn_handler = static_cast<ConnectionHandler *>(w->data);
+  const auto &old_ticket_keys = worker_config->ticket_keys;
+
+  auto ticket_keys = std::make_shared<TicketKeys>();
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "renew ticket key";
+  }
+  // We store at most 2 ticket keys
+  if (old_ticket_keys) {
+    auto &old_keys = old_ticket_keys->keys;
+    auto &new_keys = ticket_keys->keys;
+
+    assert(!old_keys.empty());
+
+    new_keys.resize(2);
+    new_keys[1] = old_keys[0];
+  } else {
+    ticket_keys->keys.resize(1);
+  }
+
+  if (RAND_bytes(reinterpret_cast<unsigned char *>(&ticket_keys->keys[0]),
+                 sizeof(ticket_keys->keys[0])) == 0) {
+    if (LOG_ENABLED(INFO)) {
+      LOG(INFO) << "failed to renew ticket key";
+    }
+    return;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "ticket keys generation done";
+    for (auto &key : ticket_keys->keys) {
+      LOG(INFO) << "name: " << util::format_hex(key.name, sizeof(key.name));
+    }
+  }
+
+  worker_config->ticket_keys = ticket_keys;
+
+  conn_handler->worker_renew_ticket_keys(ticket_keys);
+}
+} // namespace
+
+namespace {
+int event_loop() {
+  auto loop = EV_DEFAULT;
+
+  auto conn_handler = make_unique<ConnectionHandler>(loop);
+  if (get_config()->daemon) {
+    if (daemon(0, 0) == -1) {
+      auto error = errno;
+      LOG(FATAL) << "Failed to daemonize: " << strerror(error);
+      exit(EXIT_FAILURE);
+    }
+
+    // We get new PID after successful daemon().
+    mod_config()->pid = getpid();
+  }
+
+  if (get_config()->pid_file) {
+    save_pid();
+  }
+
+  auto acceptor6 = create_acceptor(conn_handler.get(), AF_INET6);
+  auto acceptor4 = create_acceptor(conn_handler.get(), AF_INET);
+  if (!acceptor6 && !acceptor4) {
+    LOG(FATAL) << "Failed to listen on address " << get_config()->host.get()
+               << ", port " << get_config()->port;
+    exit(EXIT_FAILURE);
+  }
+
+  conn_handler->set_acceptor4(std::move(acceptor4));
+  conn_handler->set_acceptor6(std::move(acceptor6));
+
+  // ListenHandler loads private key, and we listen on a priveleged port.
+  // After that, we drop the root privileges if needed.
+  drop_privileges();
+
+  ev_timer renew_ticket_key_timer;
+  if (!get_config()->client_mode && !get_config()->upstream_no_tls &&
+      get_config()->auto_tls_ticket_key) {
+    // Renew ticket key every 12hrs
+    ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 12 * 3600.);
+    renew_ticket_key_timer.data = conn_handler.get();
+    ev_timer_again(loop, &renew_ticket_key_timer);
+
+    // Generate first session ticket key before running workers.
+    renew_ticket_key_cb(loop, &renew_ticket_key_timer, 0);
+  }
+
+#ifndef NOTHREADS
+  int rv;
+  sigset_t signals;
+  sigemptyset(&signals);
+  sigaddset(&signals, REOPEN_LOG_SIGNAL);
+  sigaddset(&signals, EXEC_BINARY_SIGNAL);
+  sigaddset(&signals, GRACEFUL_SHUTDOWN_SIGNAL);
+  rv = pthread_sigmask(SIG_BLOCK, &signals, nullptr);
+  if (rv != 0) {
+    LOG(ERROR) << "Blocking signals failed: " << strerror(rv);
+  }
+#endif // !NOTHREADS
+
+  if (get_config()->num_worker > 1) {
+    if (!get_config()->tls_ctx_per_worker) {
+      conn_handler->create_ssl_context();
+    }
+    conn_handler->create_worker_thread(get_config()->num_worker);
+  } else {
+    conn_handler->create_ssl_context();
+    if (get_config()->downstream_proto == PROTO_HTTP2) {
+      conn_handler->create_http2_session();
+    } else {
+      conn_handler->create_http1_connect_blocker();
+    }
+  }
+
+#ifndef NOTHREADS
+  rv = pthread_sigmask(SIG_UNBLOCK, &signals, nullptr);
+  if (rv != 0) {
+    LOG(ERROR) << "Unblocking signals failed: " << strerror(rv);
+  }
+#endif // !NOTHREADS
+
+  ev_signal reopen_log_sig;
+  ev_signal_init(&reopen_log_sig, reopen_log_signal_cb, REOPEN_LOG_SIGNAL);
+  reopen_log_sig.data = conn_handler.get();
+  ev_signal_start(loop, &reopen_log_sig);
+
+  ev_signal exec_bin_sig;
+  ev_signal_init(&exec_bin_sig, exec_binary_signal_cb, EXEC_BINARY_SIGNAL);
+  exec_bin_sig.data = conn_handler.get();
+  ev_signal_start(loop, &exec_bin_sig);
+
+  ev_signal graceful_shutdown_sig;
+  ev_signal_init(&graceful_shutdown_sig, graceful_shutdown_signal_cb,
+                 GRACEFUL_SHUTDOWN_SIGNAL);
+  graceful_shutdown_sig.data = conn_handler.get();
+  ev_signal_start(loop, &graceful_shutdown_sig);
+
+  ev_timer refresh_timer;
+  ev_timer_init(&refresh_timer, refresh_cb, 0., 1.);
+  refresh_timer.data = conn_handler.get();
+  ev_timer_again(loop, &refresh_timer);
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "Entering event loop";
+  }
+
+  ev_run(loop, 0);
+
+  conn_handler->join_worker();
+
+  return 0;
+}
+} // namespace
+
+namespace {
+// Returns true if regular file or symbolic link |path| exists.
+bool conf_exists(const char *path) {
+  struct stat buf;
+  int rv = stat(path, &buf);
+  return rv == 0 && (buf.st_mode & (S_IFREG | S_IFLNK));
+}
+} // namespace
+
+namespace {
+const char *DEFAULT_NPN_LIST = "h2-16," NGHTTP2_PROTO_VERSION_ID ","
+#ifdef HAVE_SPDYLAY
+                               "spdy/3.1,"
+#endif // HAVE_SPDYLAY
+                               "http/1.1";
+} // namespace
+
+namespace {
+const char *DEFAULT_TLS_PROTO_LIST = "TLSv1.2,TLSv1.1";
+} // namespace
+
+namespace {
+const char *DEFAULT_ACCESSLOG_FORMAT = "$remote_addr - - [$time_local] "
+                                       "\"$request\" $status $body_bytes_sent "
+                                       "\"$http_referer\" \"$http_user_agent\"";
+} // namespace
+
+namespace {
+auto DEFAULT_DOWNSTREAM_HOST = "127.0.0.1";
+int16_t DEFAULT_DOWNSTREAM_PORT = 80;
+} // namespace;
+
+namespace {
+void fill_default_config() {
+  memset(mod_config(), 0, sizeof(*mod_config()));
+
+  mod_config()->verbose = false;
+  mod_config()->daemon = false;
+
+  mod_config()->server_name = "nghttpx nghttp2/" NGHTTP2_VERSION;
+  mod_config()->host = strcopy("*");
+  mod_config()->port = 3000;
+  mod_config()->private_key_file = nullptr;
+  mod_config()->private_key_passwd = nullptr;
+  mod_config()->cert_file = nullptr;
+
+  // Read timeout for HTTP2 upstream connection
+  mod_config()->http2_upstream_read_timeout = 180.;
+
+  // Read timeout for non-HTTP2 upstream connection
+  mod_config()->upstream_read_timeout = 180.;
+
+  // Write timeout for HTTP2/non-HTTP2 upstream connection
+  mod_config()->upstream_write_timeout = 30.;
+
+  // Read/Write timeouts for downstream connection
+  mod_config()->downstream_read_timeout = 180.;
+  mod_config()->downstream_write_timeout = 30.;
+
+  // Read timeout for HTTP/2 stream
+  mod_config()->stream_read_timeout = 0.;
+
+  // Write timeout for HTTP/2 stream
+  mod_config()->stream_write_timeout = 0.;
+
+  // Timeout for pooled (idle) connections
+  mod_config()->downstream_idle_read_timeout = 2.;
+
+  // window bits for HTTP/2 and SPDY upstream/downstream connection
+  // per stream. 2**16-1 = 64KiB-1, which is HTTP/2 default. Please
+  // note that SPDY/3 default is 64KiB.
+  mod_config()->http2_upstream_window_bits = 16;
+  mod_config()->http2_downstream_window_bits = 16;
+
+  // HTTP/2 SPDY/3.1 has connection-level flow control. The default
+  // window size for HTTP/2 is 64KiB - 1. SPDY/3's default is 64KiB
+  mod_config()->http2_upstream_connection_window_bits = 16;
+  mod_config()->http2_downstream_connection_window_bits = 16;
+
+  mod_config()->upstream_no_tls = false;
+  mod_config()->downstream_no_tls = false;
+
+  mod_config()->num_worker = 1;
+  mod_config()->http2_max_concurrent_streams = 100;
+  mod_config()->add_x_forwarded_for = false;
+  mod_config()->strip_incoming_x_forwarded_for = false;
+  mod_config()->no_via = false;
+  mod_config()->accesslog_file = nullptr;
+  mod_config()->accesslog_syslog = false;
+  mod_config()->accesslog_format = parse_log_format(DEFAULT_ACCESSLOG_FORMAT);
+#if defined(__ANDROID__) || defined(ANDROID)
+  // Android does not have /dev/stderr.  Use /proc/self/fd/2 instead.
+  mod_config()->errorlog_file = strcopy("/proc/self/fd/2");
+#else  // !__ANDROID__ && ANDROID
+  mod_config()->errorlog_file = strcopy("/dev/stderr");
+#endif // !__ANDROID__ && ANDROID
+  mod_config()->errorlog_syslog = false;
+  mod_config()->conf_path = strcopy("/etc/nghttpx/nghttpx.conf");
+  mod_config()->syslog_facility = LOG_DAEMON;
+  // Default accept() backlog
+  mod_config()->backlog = 512;
+  mod_config()->ciphers = nullptr;
+  mod_config()->http2_proxy = false;
+  mod_config()->http2_bridge = false;
+  mod_config()->client_proxy = false;
+  mod_config()->client = false;
+  mod_config()->client_mode = false;
+  mod_config()->insecure = false;
+  mod_config()->cacert = nullptr;
+  mod_config()->pid_file = nullptr;
+  mod_config()->user = nullptr;
+  mod_config()->uid = 0;
+  mod_config()->gid = 0;
+  mod_config()->pid = getpid();
+  mod_config()->backend_ipv4 = false;
+  mod_config()->backend_ipv6 = false;
+  mod_config()->downstream_http_proxy_userinfo = nullptr;
+  mod_config()->downstream_http_proxy_host = nullptr;
+  mod_config()->downstream_http_proxy_port = 0;
+  mod_config()->downstream_http_proxy_addrlen = 0;
+  mod_config()->read_rate = 0;
+  mod_config()->read_burst = 0;
+  mod_config()->write_rate = 0;
+  mod_config()->write_burst = 0;
+  mod_config()->worker_read_rate = 0;
+  mod_config()->worker_read_burst = 0;
+  mod_config()->worker_write_rate = 0;
+  mod_config()->worker_write_burst = 0;
+  mod_config()->verify_client = false;
+  mod_config()->verify_client_cacert = nullptr;
+  mod_config()->client_private_key_file = nullptr;
+  mod_config()->client_cert_file = nullptr;
+  mod_config()->http2_upstream_dump_request_header = nullptr;
+  mod_config()->http2_upstream_dump_response_header = nullptr;
+  mod_config()->http2_no_cookie_crumbling = false;
+  mod_config()->upstream_frame_debug = false;
+  mod_config()->padding = 0;
+  mod_config()->worker_frontend_connections = 0;
+
+  nghttp2_option_new(&mod_config()->http2_option);
+  nghttp2_option_set_no_auto_window_update(get_config()->http2_option, 1);
+
+  nghttp2_option_new(&mod_config()->http2_client_option);
+  nghttp2_option_set_no_auto_window_update(get_config()->http2_client_option,
+                                           1);
+  nghttp2_option_set_peer_max_concurrent_streams(
+      get_config()->http2_client_option, 100);
+
+  mod_config()->tls_proto_mask = 0;
+  mod_config()->no_location_rewrite = false;
+  mod_config()->no_host_rewrite = false;
+  mod_config()->argc = 0;
+  mod_config()->argv = nullptr;
+  mod_config()->downstream_connections_per_host = 8;
+  mod_config()->downstream_connections_per_frontend = 0;
+  mod_config()->listener_disable_timeout = 0.;
+  mod_config()->auto_tls_ticket_key = true;
+  mod_config()->tls_ctx_per_worker = false;
+  mod_config()->downstream_request_buffer_size = 16 * 1024;
+  mod_config()->downstream_response_buffer_size = 16 * 1024;
+  mod_config()->no_server_push = false;
+}
+} // namespace
+
+namespace {
+void print_version(std::ostream &out) {
+  out << get_config()->server_name << std::endl;
+}
+} // namespace
+
+namespace {
+void print_usage(std::ostream &out) {
+  out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>]
+A reverse proxy for HTTP/2, HTTP/1 and SPDY.)" << std::endl;
+}
+} // namespace
+
+namespace {
+void print_help(std::ostream &out) {
+  print_usage(out);
+  out << R"(
+  <PRIVATE_KEY>
+              Set path  to server's private key.   Required unless -p,
+              --client or --frontend-no-tls are given.
+  <CERT>      Set path  to server's certificate.  Required  unless -p,
+              --client or --frontend-no-tls are given.
+
+Options:
+  The options are categorized into several groups.
+
+Connections:
+  -b, --backend=<HOST,PORT>
+              Set backend host and port.  For HTTP/1 backend, multiple
+              backend addresses are accepted by repeating this option.
+              HTTP/2  backend   does  not  support   multiple  backend
+              addresses  and the  first occurrence  of this  option is
+              used.
+              Default: )" << DEFAULT_DOWNSTREAM_HOST << ","
+      << DEFAULT_DOWNSTREAM_PORT << R"(
+  -f, --frontend=<HOST,PORT>
+              Set  frontend  host and  port.   If  <HOST> is  '*',  it
+              assumes all addresses including both IPv4 and IPv6.
+              Default: )" << get_config()->host.get() << ","
+      << get_config()->port << R"(
+  --backlog=<N>
+              Set listen backlog size.
+              Default: )" << get_config()->backlog << R"(
+  --backend-ipv4
+              Resolve backend hostname to IPv4 address only.
+  --backend-ipv6
+              Resolve backend hostname to IPv6 address only.
+  --backend-http-proxy-uri=<URI>
+              Specify      proxy       URI      in       the      form
+              http://[<USER>:<PASS>@]<PROXY>:<PORT>.    If   a   proxy
+              requires  authentication,  specify  <USER>  and  <PASS>.
+              Note that  they must be properly  percent-encoded.  This
+              proxy  is used  when the  backend connection  is HTTP/2.
+              First,  make  a CONNECT  request  to  the proxy  and  it
+              connects  to the  backend  on behalf  of nghttpx.   This
+              forms  tunnel.   After  that, nghttpx  performs  SSL/TLS
+              handshake with  the downstream through the  tunnel.  The
+              timeouts when connecting and  making CONNECT request can
+              be     specified    by     --backend-read-timeout    and
+              --backend-write-timeout options.
+
+Performance:
+  -n, --workers=<N>
+              Set the number of worker threads.
+              Default: )" << get_config()->num_worker << R"(
+  --read-rate=<SIZE>
+              Set maximum  average read  rate on  frontend connection.
+              Setting 0 to this option means read rate is unlimited.
+              Default: )" << get_config()->read_rate << R"(
+  --read-burst=<SIZE>
+              Set  maximum read  burst  size  on frontend  connection.
+              Setting  0  to this  option  means  read burst  size  is
+              unlimited.
+              Default: )" << get_config()->read_burst << R"(
+  --write-rate=<SIZE>
+              Set maximum  average write rate on  frontend connection.
+              Setting 0 to this option means write rate is unlimited.
+              Default: )" << get_config()->write_rate << R"(
+  --write-burst=<SIZE>
+              Set  maximum write  burst size  on frontend  connection.
+              Setting  0 to  this  option means  write  burst size  is
+              unlimited.
+              Default: )" << get_config()->write_burst << R"(
+  --worker-read-rate=<SIZE>
+              Set maximum average read rate on frontend connection per
+              worker.  Setting  0 to  this option  means read  rate is
+              unlimited.  Not implemented yet.
+              Default: )" << get_config()->worker_read_rate << R"(
+  --worker-read-burst=<SIZE>
+              Set maximum  read burst size on  frontend connection per
+              worker.  Setting 0 to this  option means read burst size
+              is unlimited.  Not implemented yet.
+              Default: )" << get_config()->worker_read_burst << R"(
+  --worker-write-rate=<SIZE>
+              Set maximum  average write  rate on  frontend connection
+              per worker.  Setting  0 to this option  means write rate
+              is unlimited.  Not implemented yet.
+              Default: )" << get_config()->worker_write_rate << R"(
+  --worker-write-burst=<SIZE>
+              Set maximum write burst  size on frontend connection per
+              worker.  Setting 0 to this option means write burst size
+              is unlimited.  Not implemented yet.
+              Default: )" << get_config()->worker_write_burst << R"(
+  --worker-frontend-connections=<N>
+              Set maximum number  of simultaneous connections frontend
+              accepts.  Setting 0 means unlimited.
+              Default: )" << get_config()->worker_frontend_connections << R"(
+  --backend-http1-connections-per-host=<N>
+              Set   maximum  number   of  backend   concurrent  HTTP/1
+              connections per host.  This option is meaningful when -s
+              option is used.  To limit  the number of connections per
+              frontend        for       default        mode,       use
+              --backend-http1-connections-per-frontend.
+              Default: )" << get_config()->downstream_connections_per_host
+      << R"(
+  --backend-http1-connections-per-frontend=<N>
+              Set   maximum  number   of  backend   concurrent  HTTP/1
+              connections per frontend.  This  option is only used for
+              default mode.   0 means unlimited.  To  limit the number
+              of connections  per host for  HTTP/2 or SPDY  proxy mode
+              (-s option), use --backend-http1-connections-per-host.
+              Default: )" << get_config()->downstream_connections_per_frontend
+      << R"(
+  --rlimit-nofile=<N>
+              Set maximum number of open files (RLIMIT_NOFILE) to <N>.
+              If 0 is given, nghttpx does not set the limit.
+              Default: )" << get_config()->rlimit_nofile << R"(
+  --backend-request-buffer=<SIZE>
+              Set buffer size used to store backend request.
+              Default: )"
+      << util::utos_with_unit(get_config()->downstream_request_buffer_size)
+      << R"(
+  --backend-response-buffer=<SIZE>
+              Set buffer size used to store backend response.
+              Default: )"
+      << util::utos_with_unit(get_config()->downstream_response_buffer_size)
+      << R"(
+
+Timeout:
+  --frontend-http2-read-timeout=<DURATION>
+              Specify  read  timeout  for  HTTP/2  and  SPDY  frontend
+              connection.
+              Default: )"
+      << util::duration_str(get_config()->http2_upstream_read_timeout) << R"(
+  --frontend-read-timeout=<DURATION>
+              Specify read timeout for HTTP/1.1 frontend connection.
+              Default: )"
+      << util::duration_str(get_config()->upstream_read_timeout) << R"(
+  --frontend-write-timeout=<DURATION>
+              Specify write timeout for all frontend connections.
+              Default: )"
+      << util::duration_str(get_config()->upstream_write_timeout) << R"(
+  --stream-read-timeout=<DURATION>
+              Specify  read timeout  for HTTP/2  and SPDY  streams.  0
+              means no timeout.
+              Default: )"
+      << util::duration_str(get_config()->stream_read_timeout) << R"(
+  --stream-write-timeout=<DURATION>
+              Specify write  timeout for  HTTP/2 and SPDY  streams.  0
+              means no timeout.
+              Default: )"
+      << util::duration_str(get_config()->stream_write_timeout) << R"(
+  --backend-read-timeout=<DURATION>
+              Specify read timeout for backend connection.
+              Default: )"
+      << util::duration_str(get_config()->downstream_read_timeout) << R"(
+  --backend-write-timeout=<DURATION>
+              Specify write timeout for backend connection.
+              Default: )"
+      << util::duration_str(get_config()->downstream_write_timeout) << R"(
+  --backend-keep-alive-timeout=<DURATION>
+              Specify keep-alive timeout for backend connection.
+              Default: )"
+      << util::duration_str(get_config()->downstream_idle_read_timeout) << R"(
+  --listener-disable-timeout=<DURATION>
+              After accepting  connection failed,  connection listener
+              is disabled  for a given  amount of time.   Specifying 0
+              disables this feature.
+              Default: )"
+      << util::duration_str(get_config()->listener_disable_timeout) << R"(
+
+SSL/TLS:
+  --ciphers=<SUITE>
+              Set allowed  cipher list.  The  format of the  string is
+              described in OpenSSL ciphers(1).
+  -k, --insecure
+              Don't  verify   backend  server's  certificate   if  -p,
+              --client    or    --http2-bridge     are    given    and
+              --backend-no-tls is not given.
+  --cacert=<PATH>
+              Set path to trusted CA  certificate file if -p, --client
+              or --http2-bridge are given  and --backend-no-tls is not
+              given.  The file must be  in PEM format.  It can contain
+              multiple  certificates.    If  the  linked   OpenSSL  is
+              configured to  load system  wide certificates,  they are
+              loaded at startup regardless of this option.
+  --private-key-passwd-file=<PATH>
+              Path  to file  that contains  password for  the server's
+              private key.   If none is  given and the private  key is
+              password protected it'll be requested interactively.
+  --subcert=<KEYPATH>:<CERTPATH>
+              Specify  additional certificate  and  private key  file.
+              nghttpx will  choose certificates based on  the hostname
+              indicated  by  client  using TLS  SNI  extension.   This
+              option can be used multiple times.
+  --backend-tls-sni-field=<HOST>
+              Explicitly  set the  content of  the TLS  SNI extension.
+              This will default to the backend HOST name.
+  --dh-param-file=<PATH>
+              Path to file that contains  DH parameters in PEM format.
+              Without  this   option,  DHE   cipher  suites   are  not
+              available.
+  --npn-list=<LIST>
+              Comma delimited list of  ALPN protocol identifier sorted
+              in the  order of preference.  That  means most desirable
+              protocol comes  first.  This  is used  in both  ALPN and
+              NPN.  The parameter must be  delimited by a single comma
+              only  and any  white spaces  are  treated as  a part  of
+              protocol string.
+              Default: )" << DEFAULT_NPN_LIST << R"(
+  --verify-client
+              Require and verify client certificate.
+  --verify-client-cacert=<PATH>
+              Path  to file  that contains  CA certificates  to verify
+              client certificate.  The file must be in PEM format.  It
+              can contain multiple certificates.
+  --client-private-key-file=<PATH>
+              Path to  file that contains  client private key  used in
+              backend client authentication.
+  --client-cert-file=<PATH>
+              Path to  file that  contains client certificate  used in
+              backend client authentication.
+  --tls-proto-list=<LIST>
+              Comma delimited list of  SSL/TLS protocol to be enabled.
+              The following protocols  are available: TLSv1.2, TLSv1.1
+              and   TLSv1.0.    The   name   matching   is   done   in
+              case-insensitive   manner.    The  parameter   must   be
+              delimited by  a single comma  only and any  white spaces
+              are treated as a part of protocol string.
+              Default: )" << DEFAULT_TLS_PROTO_LIST << R"(
+  --tls-ticket-key-file=<PATH>
+              Path  to file  that  contains 48  bytes  random data  to
+              construct TLS  session ticket parameters.   This options
+              can  be  used  repeatedly  to  specify  multiple  ticket
+              parameters.  If several files  are given, only the first
+              key is used to encrypt  TLS session tickets.  Other keys
+              are accepted  but server  will issue new  session ticket
+              with  first  key.   This allows  session  key  rotation.
+              Please   note  that   key   rotation   does  not   occur
+              automatically.   User should  rearrange files  or change
+              options  values  and  restart  nghttpx  gracefully.   If
+              opening or reading given file fails, all loaded keys are
+              discarded and it is treated as if none of this option is
+              given.  If this option is not given or an error occurred
+              while  opening  or  reading  a file,  key  is  generated
+              automatically and  renewed every 12hrs.  At  most 2 keys
+              are stored in memory.
+  --tls-ctx-per-worker
+              Create OpenSSL's SSL_CTX per worker, so that no internal
+              locking is required.  This  may improve scalability with
+              multi  threaded   configuration.   If  this   option  is
+              enabled, session ID is  no longer shared accross SSL_CTX
+              objects, which means session  ID generated by one worker
+              is not acceptable by another worker.  On the other hand,
+              session ticket key is shared across all worker threads.
+
+HTTP/2 and SPDY:
+  -c, --http2-max-concurrent-streams=<N>
+              Set the maximum number of  the concurrent streams in one
+              HTTP/2 and SPDY session.
+              Default: )" << get_config()->http2_max_concurrent_streams << R"(
+  --frontend-http2-window-bits=<N>
+              Sets the  per-stream initial window size  of HTTP/2 SPDY
+              frontend connection.  For HTTP/2,  the size is 2**<N>-1.
+              For SPDY, the size is 2**<N>.
+              Default: )" << get_config()->http2_upstream_window_bits << R"(
+  --frontend-http2-connection-window-bits=<N>
+              Sets the  per-connection window size of  HTTP/2 and SPDY
+              frontend   connection.    For   HTTP/2,  the   size   is
+              2**<N>-1. For SPDY, the size is 2**<N>.
+              Default: )" << get_config()->http2_upstream_connection_window_bits
+      << R"(
+  --frontend-no-tls
+              Disable SSL/TLS on frontend connections.
+  --backend-http2-window-bits=<N>
+              Sets  the   initial  window   size  of   HTTP/2  backend
+              connection to 2**<N>-1.
+              Default: )" << get_config()->http2_downstream_window_bits << R"(
+  --backend-http2-connection-window-bits=<N>
+              Sets the  per-connection window  size of  HTTP/2 backend
+              connection to 2**<N>-1.
+              Default: )"
+      << get_config()->http2_downstream_connection_window_bits << R"(
+  --backend-no-tls
+              Disable SSL/TLS on backend connections.
+  --http2-no-cookie-crumbling
+              Don't crumble cookie header field.
+  --padding=<N>
+              Add  at most  <N> bytes  to  a HTTP/2  frame payload  as
+              padding.  Specify 0 to  disable padding.  This option is
+              meant for debugging purpose  and not intended to enhance
+              protocol security.
+  --no-server-push
+              Disable  HTTP/2  server  push.    Server  push  is  only
+              supported  by default  mode and  HTTP/2 frontend.   SPDY
+              frontend does not support server push.
+
+Mode:
+  (default mode)
+              Accept  HTTP/2,  SPDY  and HTTP/1.1  over  SSL/TLS.   If
+              --frontend-no-tls is  used, accept HTTP/2  and HTTP/1.1.
+              The  incoming HTTP/1.1  connection  can  be upgraded  to
+              HTTP/2  through  HTTP  Upgrade.   The  protocol  to  the
+              backend is HTTP/1.1.
+  -s, --http2-proxy
+              Like default mode, but enable secure proxy mode.
+  --http2-bridge
+              Like default  mode, but communicate with  the backend in
+              HTTP/2 over SSL/TLS.  Thus  the incoming all connections
+              are converted  to HTTP/2  connection and relayed  to the
+              backend.  See --backend-http-proxy-uri option if you are
+              behind  the proxy  and want  to connect  to the  outside
+              HTTP/2 proxy.
+  --client    Accept  HTTP/2   and  HTTP/1.1  without   SSL/TLS.   The
+              incoming HTTP/1.1  connection can be upgraded  to HTTP/2
+              connection through  HTTP Upgrade.   The protocol  to the
+              backend is HTTP/2.   To use nghttpx as  a forward proxy,
+              use -p option instead.
+  -p, --client-proxy
+              Like --client  option, but it also  requires the request
+              path from frontend must be an absolute URI, suitable for
+              use as a forward proxy.
+
+Logging:
+  -L, --log-level=<LEVEL>
+              Set the severity  level of log output.   <LEVEL> must be
+              one of INFO, NOTICE, WARN, ERROR and FATAL.
+              Default: NOTICE
+  --accesslog-file=<PATH>
+              Set path to write access log.  To reopen file, send USR1
+              signal to nghttpx.
+  --accesslog-syslog
+              Send  access log  to syslog.   If this  option is  used,
+              --accesslog-file option is ignored.
+  --accesslog-format=<FORMAT>
+              Specify  format  string  for access  log.   The  default
+              format is combined format.   The following variables are
+              available:
+
+              * $remote_addr: client IP address.
+              * $time_local: local time in Common Log format.
+              * $time_iso8601: local time in ISO 8601 format.
+              * $request: HTTP request line.
+              * $status: HTTP response status code.
+              * $body_bytes_sent: the  number of bytes sent  to client
+                as response body.
+              * $http_<VAR>: value of HTTP  request header <VAR> where
+                '_' in <VAR> is replaced with '-'.
+              * $remote_port: client  port.
+              * $server_port: server port.
+              * $request_time: request processing time in seconds with
+                milliseconds resolution.
+              * $pid: PID of the running process.
+              * $alpn: ALPN identifier of the protocol which generates
+                the response.   For HTTP/1,  ALPN is  always http/1.1,
+                regardless of minor version.
+
+              Default: )" << DEFAULT_ACCESSLOG_FORMAT << R"(
+  --errorlog-file=<PATH>
+              Set path to write error  log.  To reopen file, send USR1
+              signal to nghttpx.
+              Default: )" << get_config()->errorlog_file.get() << R"(
+  --errorlog-syslog
+              Send  error log  to  syslog.  If  this  option is  used,
+              --errorlog-file option is ignored.
+  --syslog-facility=<FACILITY>
+              Set syslog facility to <FACILITY>.
+              Default: )" << str_syslog_facility(get_config()->syslog_facility)
+      << R"(
+
+HTTP:
+  --add-x-forwarded-for
+              Append  X-Forwarded-For header  field to  the downstream
+              request.
+  --strip-incoming-x-forwarded-for
+              Strip X-Forwarded-For  header field from  inbound client
+              requests.
+  --no-via    Don't append to  Via header field.  If  Via header field
+              is received, it is left unaltered.
+  --no-location-rewrite
+              Don't rewrite  location header field  on --http2-bridge,
+              --client  and  default   mode.   For  --http2-proxy  and
+              --client-proxy mode,  location header field will  not be
+              altered regardless of this option.
+  --no-host-rewrite
+              Don't  rewrite  host  and :authority  header  fields  on
+              --http2-bridge,   --client   and  default   mode.    For
+              --http2-proxy  and  --client-proxy mode,  these  headers
+              will not be altered regardless of this option.
+  --altsvc=<PROTOID,PORT[,HOST,[ORIGIN]]>
+              Specify   protocol  ID,   port,  host   and  origin   of
+              alternative service.  <HOST>  and <ORIGIN> are optional.
+              They are  advertised in  alt-svc header field  or HTTP/2
+              ALTSVC frame.  This option can be used multiple times to
+              specify   multiple   alternative   services.    Example:
+              --altsvc=h2,443
+  --add-response-header=<HEADER>
+              Specify  additional  header  field to  add  to  response
+              header set.   This option just appends  header field and
+              won't replace anything already  set.  This option can be
+              used several  times to  specify multiple  header fields.
+              Example: --add-response-header="foo: bar"
+
+Debug:
+  --frontend-http2-dump-request-header=<PATH>
+              Dumps request headers received by HTTP/2 frontend to the
+              file denoted  in <PATH>.  The  output is done  in HTTP/1
+              header field format and each header block is followed by
+              an empty line.  This option  is not thread safe and MUST
+              NOT be used with option -n<N>, where <N> >= 2.
+  --frontend-http2-dump-response-header=<PATH>
+              Dumps response headers sent  from HTTP/2 frontend to the
+              file denoted  in <PATH>.  The  output is done  in HTTP/1
+              header field format and each header block is followed by
+              an empty line.  This option  is not thread safe and MUST
+              NOT be used with option -n<N>, where <N> >= 2.
+  -o, --frontend-frame-debug
+              Print HTTP/2 frames in  frontend to stderr.  This option
+              is  not thread  safe and  MUST NOT  be used  with option
+              -n=N, where N >= 2.
+
+Process:
+  -D, --daemon
+              Run in a background.  If -D is used, the current working
+              directory is changed to '/'.
+  --pid-file=<PATH>
+              Set path to save PID of this program.
+  --user=<USER>
+              Run this program as <USER>.   This option is intended to
+              be used to drop root privileges.
+
+Misc:
+  --conf=<PATH>
+              Load configuration from <PATH>.
+              Default: )" << get_config()->conf_path.get() << R"(
+  -v, --version
+              Print version and exit.
+  -h, --help  Print this help and exit.
+
+  The <SIZE> argument is an integer and an optional unit (e.g., 10K is
+  10 * 1024).  Units are K, M and G (powers of 1024).
+
+  The <DURATION> argument is an integer and an optional unit (e.g., 1s
+  is 1 second and 500ms is 500  milliseconds).  Units are s or ms.  If
+  a unit is omitted, a second is used as unit.)" << std::endl;
+}
+} // namespace
+
+int main(int argc, char **argv) {
+  Log::set_severity_level(NOTICE);
+  create_config();
+  fill_default_config();
+
+  // We have to copy argv, since getopt_long may change its content.
+  mod_config()->argc = argc;
+  mod_config()->argv = new char *[argc];
+
+  for (int i = 0; i < argc; ++i) {
+    mod_config()->argv[i] = strdup(argv[i]);
+  }
+
+  mod_config()->cwd = getcwd(nullptr, 0);
+  if (mod_config()->cwd == nullptr) {
+    auto error = errno;
+    LOG(FATAL) << "failed to get current working directory: errno=" << error;
+    exit(EXIT_FAILURE);
+  }
+
+  std::vector<std::pair<const char *, const char *>> cmdcfgs;
+  while (1) {
+    static int flag = 0;
+    static option long_options[] = {
+        {"daemon", no_argument, nullptr, 'D'},
+        {"log-level", required_argument, nullptr, 'L'},
+        {"backend", required_argument, nullptr, 'b'},
+        {"http2-max-concurrent-streams", required_argument, nullptr, 'c'},
+        {"frontend", required_argument, nullptr, 'f'},
+        {"help", no_argument, nullptr, 'h'},
+        {"insecure", no_argument, nullptr, 'k'},
+        {"workers", required_argument, nullptr, 'n'},
+        {"client-proxy", no_argument, nullptr, 'p'},
+        {"http2-proxy", no_argument, nullptr, 's'},
+        {"version", no_argument, nullptr, 'v'},
+        {"frontend-frame-debug", no_argument, nullptr, 'o'},
+        {"add-x-forwarded-for", no_argument, &flag, 1},
+        {"frontend-http2-read-timeout", required_argument, &flag, 2},
+        {"frontend-read-timeout", required_argument, &flag, 3},
+        {"frontend-write-timeout", required_argument, &flag, 4},
+        {"backend-read-timeout", required_argument, &flag, 5},
+        {"backend-write-timeout", required_argument, &flag, 6},
+        {"accesslog-file", required_argument, &flag, 7},
+        {"backend-keep-alive-timeout", required_argument, &flag, 8},
+        {"frontend-http2-window-bits", required_argument, &flag, 9},
+        {"pid-file", required_argument, &flag, 10},
+        {"user", required_argument, &flag, 11},
+        {"conf", required_argument, &flag, 12},
+        {"syslog-facility", required_argument, &flag, 14},
+        {"backlog", required_argument, &flag, 15},
+        {"ciphers", required_argument, &flag, 16},
+        {"client", no_argument, &flag, 17},
+        {"backend-http2-window-bits", required_argument, &flag, 18},
+        {"cacert", required_argument, &flag, 19},
+        {"backend-ipv4", no_argument, &flag, 20},
+        {"backend-ipv6", no_argument, &flag, 21},
+        {"private-key-passwd-file", required_argument, &flag, 22},
+        {"no-via", no_argument, &flag, 23},
+        {"subcert", required_argument, &flag, 24},
+        {"http2-bridge", no_argument, &flag, 25},
+        {"backend-http-proxy-uri", required_argument, &flag, 26},
+        {"backend-no-tls", no_argument, &flag, 27},
+        {"frontend-no-tls", no_argument, &flag, 29},
+        {"backend-tls-sni-field", required_argument, &flag, 31},
+        {"dh-param-file", required_argument, &flag, 33},
+        {"read-rate", required_argument, &flag, 34},
+        {"read-burst", required_argument, &flag, 35},
+        {"write-rate", required_argument, &flag, 36},
+        {"write-burst", required_argument, &flag, 37},
+        {"npn-list", required_argument, &flag, 38},
+        {"verify-client", no_argument, &flag, 39},
+        {"verify-client-cacert", required_argument, &flag, 40},
+        {"client-private-key-file", required_argument, &flag, 41},
+        {"client-cert-file", required_argument, &flag, 42},
+        {"frontend-http2-dump-request-header", required_argument, &flag, 43},
+        {"frontend-http2-dump-response-header", required_argument, &flag, 44},
+        {"http2-no-cookie-crumbling", no_argument, &flag, 45},
+        {"frontend-http2-connection-window-bits", required_argument, &flag, 46},
+        {"backend-http2-connection-window-bits", required_argument, &flag, 47},
+        {"tls-proto-list", required_argument, &flag, 48},
+        {"padding", required_argument, &flag, 49},
+        {"worker-read-rate", required_argument, &flag, 50},
+        {"worker-read-burst", required_argument, &flag, 51},
+        {"worker-write-rate", required_argument, &flag, 52},
+        {"worker-write-burst", required_argument, &flag, 53},
+        {"altsvc", required_argument, &flag, 54},
+        {"add-response-header", required_argument, &flag, 55},
+        {"worker-frontend-connections", required_argument, &flag, 56},
+        {"accesslog-syslog", no_argument, &flag, 57},
+        {"errorlog-file", required_argument, &flag, 58},
+        {"errorlog-syslog", no_argument, &flag, 59},
+        {"stream-read-timeout", required_argument, &flag, 60},
+        {"stream-write-timeout", required_argument, &flag, 61},
+        {"no-location-rewrite", no_argument, &flag, 62},
+        {"backend-http1-connections-per-host", required_argument, &flag, 63},
+        {"listener-disable-timeout", required_argument, &flag, 64},
+        {"strip-incoming-x-forwarded-for", no_argument, &flag, 65},
+        {"accesslog-format", required_argument, &flag, 66},
+        {"backend-http1-connections-per-frontend", required_argument, &flag,
+         67},
+        {"tls-ticket-key-file", required_argument, &flag, 68},
+        {"rlimit-nofile", required_argument, &flag, 69},
+        {"tls-ctx-per-worker", no_argument, &flag, 70},
+        {"backend-response-buffer", required_argument, &flag, 71},
+        {"backend-request-buffer", required_argument, &flag, 72},
+        {"no-host-rewrite", no_argument, &flag, 73},
+        {"no-server-push", no_argument, &flag, 74},
+        {nullptr, 0, nullptr, 0}};
+
+    int option_index = 0;
+    int c = getopt_long(argc, argv, "DL:b:c:f:hkn:opsv", long_options,
+                        &option_index);
+    if (c == -1) {
+      break;
+    }
+    switch (c) {
+    case 'D':
+      cmdcfgs.emplace_back(SHRPX_OPT_DAEMON, "yes");
+      break;
+    case 'L':
+      cmdcfgs.emplace_back(SHRPX_OPT_LOG_LEVEL, optarg);
+      break;
+    case 'b':
+      cmdcfgs.emplace_back(SHRPX_OPT_BACKEND, optarg);
+      break;
+    case 'c':
+      cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS, optarg);
+      break;
+    case 'f':
+      cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND, optarg);
+      break;
+    case 'h':
+      print_help(std::cout);
+      exit(EXIT_SUCCESS);
+    case 'k':
+      cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, "yes");
+      break;
+    case 'n':
+#ifdef NOTHREADS
+      LOG(WARN) << "Threading disabled at build time, no threads created.";
+#else
+      cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, optarg);
+#endif // NOTHREADS
+      break;
+    case 'o':
+      cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG, "yes");
+      break;
+    case 'p':
+      cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, "yes");
+      break;
+    case 's':
+      cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_PROXY, "yes");
+      break;
+    case 'v':
+      print_version(std::cout);
+      exit(EXIT_SUCCESS);
+    case '?':
+      util::show_candidates(argv[optind - 1], long_options);
+      exit(EXIT_FAILURE);
+    case 0:
+      switch (flag) {
+      case 1:
+        // --add-x-forwarded-for
+        cmdcfgs.emplace_back(SHRPX_OPT_ADD_X_FORWARDED_FOR, "yes");
+        break;
+      case 2:
+        // --frontend-http2-read-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT, optarg);
+        break;
+      case 3:
+        // --frontend-read-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_READ_TIMEOUT, optarg);
+        break;
+      case 4:
+        // --frontend-write-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT, optarg);
+        break;
+      case 5:
+        // --backend-read-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_READ_TIMEOUT, optarg);
+        break;
+      case 6:
+        // --backend-write-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_WRITE_TIMEOUT, optarg);
+        break;
+      case 7:
+        cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FILE, optarg);
+        break;
+      case 8:
+        // --backend-keep-alive-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT, optarg);
+        break;
+      case 9:
+        // --frontend-http2-window-bits
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS, optarg);
+        break;
+      case 10:
+        cmdcfgs.emplace_back(SHRPX_OPT_PID_FILE, optarg);
+        break;
+      case 11:
+        cmdcfgs.emplace_back(SHRPX_OPT_USER, optarg);
+        break;
+      case 12:
+        // --conf
+        mod_config()->conf_path = strcopy(optarg);
+        break;
+      case 14:
+        // --syslog-facility
+        cmdcfgs.emplace_back(SHRPX_OPT_SYSLOG_FACILITY, optarg);
+        break;
+      case 15:
+        // --backlog
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKLOG, optarg);
+        break;
+      case 16:
+        // --ciphers
+        cmdcfgs.emplace_back(SHRPX_OPT_CIPHERS, optarg);
+        break;
+      case 17:
+        // --client
+        cmdcfgs.emplace_back(SHRPX_OPT_CLIENT, "yes");
+        break;
+      case 18:
+        // --backend-http2-window-bits
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS, optarg);
+        break;
+      case 19:
+        // --cacert
+        cmdcfgs.emplace_back(SHRPX_OPT_CACERT, optarg);
+        break;
+      case 20:
+        // --backend-ipv4
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV4, "yes");
+        break;
+      case 21:
+        // --backend-ipv6
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV6, "yes");
+        break;
+      case 22:
+        // --private-key-passwd-file
+        cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE, optarg);
+        break;
+      case 23:
+        // --no-via
+        cmdcfgs.emplace_back(SHRPX_OPT_NO_VIA, "yes");
+        break;
+      case 24:
+        // --subcert
+        cmdcfgs.emplace_back(SHRPX_OPT_SUBCERT, optarg);
+        break;
+      case 25:
+        // --http2-bridge
+        cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_BRIDGE, "yes");
+        break;
+      case 26:
+        // --backend-http-proxy-uri
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP_PROXY_URI, optarg);
+        break;
+      case 27:
+        // --backend-no-tls
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_NO_TLS, "yes");
+        break;
+      case 29:
+        // --frontend-no-tls
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_NO_TLS, "yes");
+        break;
+      case 31:
+        // --backend-tls-sni-field
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SNI_FIELD, optarg);
+        break;
+      case 33:
+        // --dh-param-file
+        cmdcfgs.emplace_back(SHRPX_OPT_DH_PARAM_FILE, optarg);
+        break;
+      case 34:
+        // --read-rate
+        cmdcfgs.emplace_back(SHRPX_OPT_READ_RATE, optarg);
+        break;
+      case 35:
+        // --read-burst
+        cmdcfgs.emplace_back(SHRPX_OPT_READ_BURST, optarg);
+        break;
+      case 36:
+        // --write-rate
+        cmdcfgs.emplace_back(SHRPX_OPT_WRITE_RATE, optarg);
+        break;
+      case 37:
+        // --write-burst
+        cmdcfgs.emplace_back(SHRPX_OPT_WRITE_BURST, optarg);
+        break;
+      case 38:
+        // --npn-list
+        cmdcfgs.emplace_back(SHRPX_OPT_NPN_LIST, optarg);
+        break;
+      case 39:
+        // --verify-client
+        cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT, "yes");
+        break;
+      case 40:
+        // --verify-client-cacert
+        cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_CACERT, optarg);
+        break;
+      case 41:
+        // --client-private-key-file
+        cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE, optarg);
+        break;
+      case 42:
+        // --client-cert-file
+        cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CERT_FILE, optarg);
+        break;
+      case 43:
+        // --frontend-http2-dump-request-header
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER,
+                             optarg);
+        break;
+      case 44:
+        // --frontend-http2-dump-response-header
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER,
+                             optarg);
+        break;
+      case 45:
+        // --http2-no-cookie-crumbling
+        cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING, "yes");
+        break;
+      case 46:
+        // --frontend-http2-connection-window-bits
+        cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS,
+                             optarg);
+        break;
+      case 47:
+        // --backend-http2-connection-window-bits
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS,
+                             optarg);
+        break;
+      case 48:
+        // --tls-proto-list
+        cmdcfgs.emplace_back(SHRPX_OPT_TLS_PROTO_LIST, optarg);
+        break;
+      case 49:
+        // --padding
+        cmdcfgs.emplace_back(SHRPX_OPT_PADDING, optarg);
+        break;
+      case 50:
+        // --worker-read-rate
+        cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_RATE, optarg);
+        break;
+      case 51:
+        // --worker-read-burst
+        cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_BURST, optarg);
+        break;
+      case 52:
+        // --worker-write-rate
+        cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_RATE, optarg);
+        break;
+      case 53:
+        // --worker-write-burst
+        cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_BURST, optarg);
+        break;
+      case 54:
+        // --altsvc
+        cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, optarg);
+        break;
+      case 55:
+        // --add-response-header
+        cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, optarg);
+        break;
+      case 56:
+        // --worker-frontend-connections
+        cmdcfgs.emplace_back(SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS, optarg);
+        break;
+      case 57:
+        // --accesslog-syslog
+        cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_SYSLOG, "yes");
+        break;
+      case 58:
+        // --errorlog-file
+        cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_FILE, optarg);
+        break;
+      case 59:
+        // --errorlog-syslog
+        cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_SYSLOG, "yes");
+        break;
+      case 60:
+        // --stream-read-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_STREAM_READ_TIMEOUT, optarg);
+        break;
+      case 61:
+        // --stream-write-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_STREAM_WRITE_TIMEOUT, optarg);
+        break;
+      case 62:
+        // --no-location-rewrite
+        cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE, "yes");
+        break;
+      case 63:
+        // --backend-http1-connections-per-host
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
+                             optarg);
+        break;
+      case 64:
+        // --listener-disable-timeout
+        cmdcfgs.emplace_back(SHRPX_OPT_LISTENER_DISABLE_TIMEOUT, optarg);
+        break;
+      case 65:
+        // --strip-incoming-x-forwarded-for
+        cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR, "yes");
+        break;
+      case 66:
+        // --accesslog-format
+        cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, optarg);
+        break;
+      case 67:
+        // --backend-http1-connections-per-frontend
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND,
+                             optarg);
+        break;
+      case 68:
+        // --tls-ticket-key-file
+        cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, optarg);
+        break;
+      case 69:
+        // --rlimit-nofile
+        cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_NOFILE, optarg);
+        break;
+      case 70:
+        // --tls-ctx-per-worker
+        cmdcfgs.emplace_back(SHRPX_OPT_TLS_CTX_PER_WORKER, "yes");
+        break;
+      case 71:
+        // --backend-response-buffer
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_RESPONSE_BUFFER, optarg);
+        break;
+      case 72:
+        // --backend-request-buffer
+        cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_REQUEST_BUFFER, optarg);
+        break;
+      case 73:
+        // --no-host-rewrite
+        cmdcfgs.emplace_back(SHRPX_OPT_NO_HOST_REWRITE, "yes");
+        break;
+      case 74:
+        // --no-server-push
+        cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_PUSH, "yes");
+        break;
+      default:
+        break;
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  // Initialize OpenSSL before parsing options because we create
+  // SSL_CTX there.
+  OPENSSL_config(nullptr);
+  OpenSSL_add_all_algorithms();
+  SSL_load_error_strings();
+  SSL_library_init();
+
+  if (conf_exists(get_config()->conf_path.get())) {
+    if (load_config(get_config()->conf_path.get()) == -1) {
+      LOG(FATAL) << "Failed to load configuration from "
+                 << get_config()->conf_path.get();
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (argc - optind >= 2) {
+    cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_FILE, argv[optind++]);
+    cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, argv[optind++]);
+  }
+
+  // First open default log files to deal with errors occurred while
+  // parsing option values.
+  reopen_log_files();
+
+  for (size_t i = 0, len = cmdcfgs.size(); i < len; ++i) {
+    if (parse_config(cmdcfgs[i].first, cmdcfgs[i].second) == -1) {
+      LOG(FATAL) << "Failed to parse command-line argument.";
+      exit(EXIT_FAILURE);
+    }
+  }
+
+#ifndef NOTHREADS
+  std::unique_ptr<nghttp2::ssl::LibsslGlobalLock> lock;
+  if (!get_config()->tls_ctx_per_worker) {
+    lock = make_unique<nghttp2::ssl::LibsslGlobalLock>();
+  }
+#endif // NOTHREADS
+
+  if (get_config()->accesslog_syslog || get_config()->errorlog_syslog) {
+    openlog("nghttpx", LOG_NDELAY | LOG_NOWAIT | LOG_PID,
+            get_config()->syslog_facility);
+  }
+
+  if (reopen_log_files() != 0) {
+    LOG(FATAL) << "Failed to open log file";
+    exit(EXIT_FAILURE);
+  }
+
+  if (get_config()->uid != 0) {
+    if (worker_config->accesslog_fd != -1 &&
+        fchown(worker_config->accesslog_fd, get_config()->uid,
+               get_config()->gid) == -1) {
+      auto error = errno;
+      LOG(WARN) << "Changing owner of access log file failed: "
+                << strerror(error);
+    }
+    if (worker_config->errorlog_fd != -1 &&
+        fchown(worker_config->errorlog_fd, get_config()->uid,
+               get_config()->gid) == -1) {
+      auto error = errno;
+      LOG(WARN) << "Changing owner of error log file failed: "
+                << strerror(error);
+    }
+  }
+
+  if (get_config()->http2_upstream_dump_request_header_file) {
+    auto path = get_config()->http2_upstream_dump_request_header_file.get();
+    auto f = open_file_for_write(path);
+
+    if (f == nullptr) {
+      LOG(FATAL) << "Failed to open http2 upstream request header file: "
+                 << path;
+      exit(EXIT_FAILURE);
+    }
+
+    mod_config()->http2_upstream_dump_request_header = f;
+
+    if (get_config()->uid != 0) {
+      if (chown(path, get_config()->uid, get_config()->gid) == -1) {
+        auto error = errno;
+        LOG(WARN) << "Changing owner of http2 upstream request header file "
+                  << path << " failed: " << strerror(error);
+      }
+    }
+  }
+
+  if (get_config()->http2_upstream_dump_response_header_file) {
+    auto path = get_config()->http2_upstream_dump_response_header_file.get();
+    auto f = open_file_for_write(path);
+
+    if (f == nullptr) {
+      LOG(FATAL) << "Failed to open http2 upstream response header file: "
+                 << path;
+      exit(EXIT_FAILURE);
+    }
+
+    mod_config()->http2_upstream_dump_response_header = f;
+
+    if (get_config()->uid != 0) {
+      if (chown(path, get_config()->uid, get_config()->gid) == -1) {
+        auto error = errno;
+        LOG(WARN) << "Changing owner of http2 upstream response header file"
+                  << " " << path << " failed: " << strerror(error);
+      }
+    }
+  }
+
+  if (get_config()->npn_list.empty()) {
+    mod_config()->npn_list = parse_config_str_list(DEFAULT_NPN_LIST);
+  }
+  if (get_config()->tls_proto_list.empty()) {
+    mod_config()->tls_proto_list =
+        parse_config_str_list(DEFAULT_TLS_PROTO_LIST);
+  }
+
+  mod_config()->tls_proto_mask =
+      ssl::create_tls_proto_mask(get_config()->tls_proto_list);
+
+  mod_config()->alpn_prefs = ssl::set_alpn_prefs(get_config()->npn_list);
+
+  if (!get_config()->tls_ticket_key_files.empty()) {
+    auto ticket_keys =
+        read_tls_ticket_key_file(get_config()->tls_ticket_key_files);
+    if (!ticket_keys) {
+      LOG(WARN) << "Use internal session ticket key generator";
+    } else {
+      worker_config->ticket_keys = std::move(ticket_keys);
+      mod_config()->auto_tls_ticket_key = false;
+    }
+  }
+
+  if (get_config()->backend_ipv4 && get_config()->backend_ipv6) {
+    LOG(FATAL) << "--backend-ipv4 and --backend-ipv6 cannot be used at the "
+               << "same time.";
+    exit(EXIT_FAILURE);
+  }
+
+  if (get_config()->worker_frontend_connections == 0) {
+    mod_config()->worker_frontend_connections =
+        std::numeric_limits<size_t>::max();
+  }
+
+  if (get_config()->http2_proxy + get_config()->http2_bridge +
+          get_config()->client_proxy + get_config()->client >
+      1) {
+    LOG(FATAL) << "--http2-proxy, --http2-bridge, --client-proxy and --client "
+               << "cannot be used at the same time.";
+    exit(EXIT_FAILURE);
+  }
+
+  if (get_config()->client || get_config()->client_proxy) {
+    mod_config()->client_mode = true;
+  }
+
+  if (get_config()->client_mode || get_config()->http2_bridge) {
+    mod_config()->downstream_proto = PROTO_HTTP2;
+  } else {
+    mod_config()->downstream_proto = PROTO_HTTP;
+  }
+
+  if (!get_config()->client_mode && !get_config()->upstream_no_tls) {
+    if (!get_config()->private_key_file || !get_config()->cert_file) {
+      print_usage(std::cerr);
+      LOG(FATAL) << "Too few arguments";
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (get_config()->downstream_addrs.empty()) {
+    DownstreamAddr addr;
+    addr.host = strcopy(DEFAULT_DOWNSTREAM_HOST);
+    addr.port = DEFAULT_DOWNSTREAM_PORT;
+
+    mod_config()->downstream_addrs.push_back(std::move(addr));
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "Resolving backend address";
+  }
+
+  for (auto &addr : mod_config()->downstream_addrs) {
+    auto ipv6 = util::ipv6_numeric_addr(addr.host.get());
+    std::string hostport;
+
+    if (ipv6) {
+      hostport += "[";
+    }
+
+    hostport += addr.host.get();
+
+    if (ipv6) {
+      hostport += "]";
+    }
+
+    hostport += ":";
+    hostport += util::utos(addr.port);
+
+    addr.hostport = strcopy(hostport);
+
+    if (resolve_hostname(
+            &addr.addr, &addr.addrlen, addr.host.get(), addr.port,
+            get_config()->backend_ipv4
+                ? AF_INET
+                : (get_config()->backend_ipv6 ? AF_INET6 : AF_UNSPEC)) == -1) {
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (get_config()->downstream_http_proxy_host) {
+    if (LOG_ENABLED(INFO)) {
+      LOG(INFO) << "Resolving backend http proxy address";
+    }
+    if (resolve_hostname(&mod_config()->downstream_http_proxy_addr,
+                         &mod_config()->downstream_http_proxy_addrlen,
+                         get_config()->downstream_http_proxy_host.get(),
+                         get_config()->downstream_http_proxy_port,
+                         AF_UNSPEC) == -1) {
+      exit(EXIT_FAILURE);
+    }
+  }
+
+  if (get_config()->rlimit_nofile) {
+    struct rlimit lim = {get_config()->rlimit_nofile,
+                         get_config()->rlimit_nofile};
+    if (setrlimit(RLIMIT_NOFILE, &lim) != 0) {
+      auto error = errno;
+      LOG(WARN) << "Setting rlimit-nofile failed: " << strerror(error);
+    }
+  }
+
+  if (get_config()->upstream_frame_debug) {
+    // To make it sync to logging
+    set_output(stderr);
+    if (isatty(fileno(stdout))) {
+      set_color_output(true);
+    }
+    reset_timer();
+  }
+
+  struct sigaction act;
+  memset(&act, 0, sizeof(struct sigaction));
+  act.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &act, nullptr);
+  sigaction(SIGCHLD, &act, nullptr);
+
+  event_loop();
+
+  LOG(NOTICE) << "Shutdown momentarily";
+
+  return 0;
+}
+
+} // namespace shrpx
+
+int main(int argc, char **argv) { return shrpx::main(argc, argv); }
diff --git a/src/shrpx.h b/src/shrpx.h
new file mode 100644 (file)
index 0000000..85c4d34
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_H
+#define SHRPX_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif // HAVE_CONFIG_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <cassert>
+
+#include "shrpx_log.h"
+
+#ifndef HAVE__EXIT
+#define _Exit(status) _exit(status)
+#endif // !HAVE__EXIT
+
+#define DIE() exit(EXIT_FAILURE)
+
+#define SHRPX_READ_WATERMARK (16 * 1024)
+
+#endif // SHRPX_H
diff --git a/src/shrpx_accept_handler.cc b/src/shrpx_accept_handler.cc
new file mode 100644 (file)
index 0000000..3be8720
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_accept_handler.h"
+
+#include <unistd.h>
+
+#include <cerrno>
+
+#include "shrpx_connection_handler.h"
+#include "shrpx_config.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void acceptcb(struct ev_loop *loop, ev_io *w, int revent) {
+  auto h = static_cast<AcceptHandler *>(w->data);
+  h->accept_connection();
+}
+} // namespace
+
+AcceptHandler::AcceptHandler(int fd, ConnectionHandler *h)
+    : conn_hnr_(h), fd_(fd) {
+  ev_io_init(&wev_, acceptcb, fd_, EV_READ);
+  wev_.data = this;
+  ev_io_start(conn_hnr_->get_loop(), &wev_);
+}
+
+AcceptHandler::~AcceptHandler() {
+  ev_io_stop(conn_hnr_->get_loop(), &wev_);
+  close(fd_);
+}
+
+void AcceptHandler::accept_connection() {
+  for (;;) {
+    sockaddr_union sockaddr;
+    socklen_t addrlen = sizeof(sockaddr);
+
+#ifdef HAVE_ACCEPT4
+    auto cfd =
+        accept4(fd_, &sockaddr.sa, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
+#else  // !HAVE_ACCEPT4
+    auto cfd = accept(fd_, &sockaddr.sa, &addrlen);
+#endif // !HAVE_ACCEPT4
+
+    if (cfd == -1) {
+      switch (errno) {
+      case EINTR:
+      case ENETDOWN:
+      case EPROTO:
+      case ENOPROTOOPT:
+      case EHOSTDOWN:
+#ifdef ENONET
+      case ENONET:
+#endif // ENONET
+      case EHOSTUNREACH:
+      case EOPNOTSUPP:
+      case ENETUNREACH:
+        continue;
+      }
+
+      break;
+    }
+
+#ifndef HAVE_ACCEPT4
+    util::make_socket_nonblocking(cfd);
+    util::make_socket_closeonexec(cfd);
+#endif // !HAVE_ACCEPT4
+
+    util::make_socket_nodelay(cfd);
+
+    conn_hnr_->handle_connection(cfd, &sockaddr.sa, addrlen);
+  }
+}
+
+void AcceptHandler::enable() { ev_io_start(conn_hnr_->get_loop(), &wev_); }
+
+void AcceptHandler::disable() { ev_io_stop(conn_hnr_->get_loop(), &wev_); }
+
+int AcceptHandler::get_fd() const { return fd_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_accept_handler.h b/src/shrpx_accept_handler.h
new file mode 100644 (file)
index 0000000..194788b
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_ACCEPT_HANDLER_H
+#define SHRPX_ACCEPT_HANDLER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+namespace shrpx {
+
+class ConnectionHandler;
+
+class AcceptHandler {
+public:
+  AcceptHandler(int fd, ConnectionHandler *h);
+  ~AcceptHandler();
+  void accept_connection();
+  void enable();
+  void disable();
+  int get_fd() const;
+
+private:
+  ev_io wev_;
+  ConnectionHandler *conn_hnr_;
+  int fd_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ACCEPT_HANDLER_H
diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc
new file mode 100644 (file)
index 0000000..a70d466
--- /dev/null
@@ -0,0 +1,732 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_client_handler.h"
+
+#include <unistd.h>
+#include <cerrno>
+
+#include "shrpx_upstream.h"
+#include "shrpx_http2_upstream.h"
+#include "shrpx_https_upstream.h"
+#include "shrpx_config.h"
+#include "shrpx_http_downstream_connection.h"
+#include "shrpx_http2_downstream_connection.h"
+#include "shrpx_ssl.h"
+#include "shrpx_worker.h"
+#include "shrpx_worker_config.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_downstream.h"
+#ifdef HAVE_SPDYLAY
+#include "shrpx_spdy_upstream.h"
+#endif // HAVE_SPDYLAY
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto handler = static_cast<ClientHandler *>(conn->data);
+
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, handler) << "Time out";
+  }
+
+  delete handler;
+}
+} // namespace
+
+namespace {
+void shutdowncb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto handler = static_cast<ClientHandler *>(w->data);
+
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, handler) << "Close connection due to TLS renegotiation";
+  }
+
+  delete handler;
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto handler = static_cast<ClientHandler *>(conn->data);
+
+  if (handler->do_read() != 0) {
+    delete handler;
+    return;
+  }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto handler = static_cast<ClientHandler *>(conn->data);
+
+  if (handler->do_write() != 0) {
+    delete handler;
+    return;
+  }
+}
+} // namespace
+
+int ClientHandler::read_clear() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  for (;;) {
+    // we should process buffered data first before we read EOF.
+    if (rb_.rleft() && on_read() != 0) {
+      return -1;
+    }
+    if (rb_.rleft()) {
+      return 0;
+    }
+    rb_.reset();
+
+    auto nread = conn_.read_clear(rb_.last, rb_.wleft());
+
+    if (nread == 0) {
+      return 0;
+    }
+
+    if (nread < 0) {
+      return -1;
+    }
+
+    rb_.write(nread);
+  }
+}
+
+int ClientHandler::write_clear() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  for (;;) {
+    if (wb_.rleft() > 0) {
+      auto nwrite = conn_.write_clear(wb_.pos, wb_.rleft());
+      if (nwrite == 0) {
+        return 0;
+      }
+      if (nwrite < 0) {
+        return -1;
+      }
+      wb_.drain(nwrite);
+      continue;
+    }
+    wb_.reset();
+    if (on_write() != 0) {
+      return -1;
+    }
+    if (wb_.rleft() == 0) {
+      break;
+    }
+  }
+
+  conn_.wlimit.stopw();
+  ev_timer_stop(conn_.loop, &conn_.wt);
+
+  return 0;
+}
+
+int ClientHandler::tls_handshake() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  ERR_clear_error();
+
+  auto rv = conn_.tls_handshake();
+
+  if (rv == SHRPX_ERR_INPROGRESS) {
+    return 0;
+  }
+
+  if (rv < 0) {
+    return -1;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "SSL/TLS handshake completed";
+  }
+
+  if (validate_next_proto() != 0) {
+    return -1;
+  }
+
+  read_ = &ClientHandler::read_tls;
+  write_ = &ClientHandler::write_tls;
+
+  return 0;
+}
+
+int ClientHandler::read_tls() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  ERR_clear_error();
+
+  for (;;) {
+    // we should process buffered data first before we read EOF.
+    if (rb_.rleft() && on_read() != 0) {
+      return -1;
+    }
+    if (rb_.rleft()) {
+      return 0;
+    }
+    rb_.reset();
+
+    auto nread = conn_.read_tls(rb_.last, rb_.wleft());
+
+    if (nread == 0) {
+      return 0;
+    }
+
+    if (nread < 0) {
+      return -1;
+    }
+
+    rb_.write(nread);
+  }
+}
+
+int ClientHandler::write_tls() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  ERR_clear_error();
+
+  for (;;) {
+    if (wb_.rleft() > 0) {
+      auto nwrite = conn_.write_tls(wb_.pos, wb_.rleft());
+
+      if (nwrite == 0) {
+        return 0;
+      }
+
+      if (nwrite < 0) {
+        return -1;
+      }
+
+      wb_.drain(nwrite);
+
+      continue;
+    }
+    wb_.reset();
+    if (on_write() != 0) {
+      return -1;
+    }
+    if (wb_.rleft() == 0) {
+      break;
+    }
+  }
+
+  conn_.wlimit.stopw();
+  ev_timer_stop(conn_.loop, &conn_.wt);
+
+  return 0;
+}
+
+int ClientHandler::upstream_noop() { return 0; }
+
+int ClientHandler::upstream_read() {
+  assert(upstream_);
+  if (upstream_->on_read() != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+int ClientHandler::upstream_write() {
+  assert(upstream_);
+  if (upstream_->on_write() != 0) {
+    return -1;
+  }
+
+  if (get_should_close_after_write() && wb_.rleft() == 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int ClientHandler::upstream_http2_connhd_read() {
+  auto nread = std::min(left_connhd_len_, rb_.rleft());
+  if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
+                 NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
+             rb_.pos, nread) != 0) {
+    // There is no downgrade path here. Just drop the connection.
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, this) << "invalid client connection header";
+    }
+
+    return -1;
+  }
+
+  left_connhd_len_ -= nread;
+  rb_.drain(nread);
+
+  if (left_connhd_len_ == 0) {
+    on_read_ = &ClientHandler::upstream_read;
+    // Run on_read to process data left in buffer since they are not
+    // notified further
+    if (on_read() != 0) {
+      return -1;
+    }
+    return 0;
+  }
+
+  return 0;
+}
+
+int ClientHandler::upstream_http1_connhd_read() {
+  auto nread = std::min(left_connhd_len_, rb_.rleft());
+  if (memcmp(NGHTTP2_CLIENT_CONNECTION_PREFACE +
+                 NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - left_connhd_len_,
+             rb_.pos, nread) != 0) {
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, this) << "This is HTTP/1.1 connection, "
+                       << "but may be upgraded to HTTP/2 later.";
+    }
+
+    // Reset header length for later HTTP/2 upgrade
+    left_connhd_len_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN;
+    on_read_ = &ClientHandler::upstream_read;
+    on_write_ = &ClientHandler::upstream_write;
+
+    if (on_read() != 0) {
+      return -1;
+    }
+
+    return 0;
+  }
+
+  left_connhd_len_ -= nread;
+  rb_.drain(nread);
+
+  if (left_connhd_len_ == 0) {
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, this) << "direct HTTP/2 connection";
+    }
+
+    direct_http2_upgrade();
+    on_read_ = &ClientHandler::upstream_read;
+    on_write_ = &ClientHandler::upstream_write;
+
+    // Run on_read to process data left in buffer since they are not
+    // notified further
+    if (on_read() != 0) {
+      return -1;
+    }
+
+    return 0;
+  }
+
+  return 0;
+}
+
+ClientHandler::ClientHandler(struct ev_loop *loop, int fd, SSL *ssl,
+                             const char *ipaddr, const char *port,
+                             WorkerStat *worker_stat,
+                             DownstreamConnectionPool *dconn_pool)
+    : conn_(loop, fd, ssl, get_config()->upstream_write_timeout,
+            get_config()->upstream_read_timeout, get_config()->write_rate,
+            get_config()->write_burst, get_config()->read_rate,
+            get_config()->read_burst, writecb, readcb, timeoutcb, this),
+      ipaddr_(ipaddr), port_(port), dconn_pool_(dconn_pool),
+      http2session_(nullptr), http1_connect_blocker_(nullptr),
+      worker_stat_(worker_stat),
+      left_connhd_len_(NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN),
+      should_close_after_write_(false) {
+
+  ++worker_stat->num_connections;
+
+  ev_timer_init(&reneg_shutdown_timer_, shutdowncb, 0., 0.);
+
+  reneg_shutdown_timer_.data = this;
+
+  conn_.rlimit.startw();
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  if (conn_.tls.ssl) {
+    SSL_set_app_data(conn_.tls.ssl, &conn_);
+    read_ = write_ = &ClientHandler::tls_handshake;
+    on_read_ = &ClientHandler::upstream_noop;
+    on_write_ = &ClientHandler::upstream_write;
+  } else {
+    // For non-TLS version, first create HttpsUpstream. It may be
+    // upgraded to HTTP/2 through HTTP Upgrade or direct HTTP/2
+    // connection.
+    upstream_ = make_unique<HttpsUpstream>(this);
+    alpn_ = "http/1.1";
+    read_ = &ClientHandler::read_clear;
+    write_ = &ClientHandler::write_clear;
+    on_read_ = &ClientHandler::upstream_http1_connhd_read;
+    on_write_ = &ClientHandler::upstream_noop;
+  }
+}
+
+ClientHandler::~ClientHandler() {
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "Deleting";
+  }
+
+  if (upstream_) {
+    upstream_->on_handler_delete();
+  }
+
+  --worker_stat_->num_connections;
+
+  ev_timer_stop(conn_.loop, &reneg_shutdown_timer_);
+
+  // TODO If backend is http/2, and it is in CONNECTED state, signal
+  // it and make it loopbreak when output is zero.
+  if (worker_config->graceful_shutdown && worker_stat_->num_connections == 0) {
+    ev_break(conn_.loop);
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "Deleted";
+  }
+}
+
+Upstream *ClientHandler::get_upstream() { return upstream_.get(); }
+
+struct ev_loop *ClientHandler::get_loop() const {
+  return conn_.loop;
+}
+
+void ClientHandler::reset_upstream_read_timeout(ev_tstamp t) {
+  conn_.rt.repeat = t;
+  if (ev_is_active(&conn_.rt)) {
+    ev_timer_again(conn_.loop, &conn_.rt);
+  }
+}
+
+void ClientHandler::reset_upstream_write_timeout(ev_tstamp t) {
+  conn_.wt.repeat = t;
+  if (ev_is_active(&conn_.wt)) {
+    ev_timer_again(conn_.loop, &conn_.wt);
+  }
+}
+
+int ClientHandler::validate_next_proto() {
+  const unsigned char *next_proto = nullptr;
+  unsigned int next_proto_len;
+  int rv;
+
+  // First set callback for catch all cases
+  on_read_ = &ClientHandler::upstream_read;
+
+  SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
+  for (int i = 0; i < 2; ++i) {
+    if (next_proto) {
+      if (LOG_ENABLED(INFO)) {
+        std::string proto(next_proto, next_proto + next_proto_len);
+        CLOG(INFO, this) << "The negotiated next protocol: " << proto;
+      }
+      if (!ssl::in_proto_list(get_config()->npn_list, next_proto,
+                              next_proto_len)) {
+        break;
+      }
+      if (util::check_h2_is_selected(next_proto, next_proto_len) ||
+          (next_proto_len == sizeof("h2-16") - 1 &&
+           memcmp("h2-16", next_proto, next_proto_len) == 0)) {
+
+        on_read_ = &ClientHandler::upstream_http2_connhd_read;
+
+        auto http2_upstream = make_unique<Http2Upstream>(this);
+
+        if (!ssl::check_http2_requirement(conn_.tls.ssl)) {
+          rv = http2_upstream->terminate_session(NGHTTP2_INADEQUATE_SECURITY);
+
+          if (rv != 0) {
+            return -1;
+          }
+        }
+
+        upstream_ = std::move(http2_upstream);
+        alpn_.assign(next_proto, next_proto + next_proto_len);
+
+        // At this point, input buffer is already filled with some
+        // bytes.  The read callback is not called until new data
+        // come. So consume input buffer here.
+        if (on_read() != 0) {
+          return -1;
+        }
+
+        return 0;
+      } else {
+#ifdef HAVE_SPDYLAY
+        uint16_t version = spdylay_npn_get_version(next_proto, next_proto_len);
+        if (version) {
+          upstream_ = make_unique<SpdyUpstream>(version, this);
+
+          switch (version) {
+          case SPDYLAY_PROTO_SPDY2:
+            alpn_ = "spdy/2";
+            break;
+          case SPDYLAY_PROTO_SPDY3:
+            alpn_ = "spdy/3";
+            break;
+          case SPDYLAY_PROTO_SPDY3_1:
+            alpn_ = "spdy/3.1";
+            break;
+          default:
+            alpn_ = "spdy/unknown";
+          }
+
+          // At this point, input buffer is already filled with some
+          // bytes.  The read callback is not called until new data
+          // come. So consume input buffer here.
+          if (on_read() != 0) {
+            return -1;
+          }
+
+          return 0;
+        }
+#endif // HAVE_SPDYLAY
+        if (next_proto_len == 8 && memcmp("http/1.1", next_proto, 8) == 0) {
+          upstream_ = make_unique<HttpsUpstream>(this);
+          alpn_ = "http/1.1";
+
+          // At this point, input buffer is already filled with some
+          // bytes.  The read callback is not called until new data
+          // come. So consume input buffer here.
+          if (on_read() != 0) {
+            return -1;
+          }
+
+          return 0;
+        }
+      }
+      break;
+    }
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+    SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+#else  // OPENSSL_VERSION_NUMBER < 0x10002000L
+    break;
+#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
+  }
+  if (!next_proto) {
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, this) << "No protocol negotiated. Fallback to HTTP/1.1";
+    }
+    upstream_ = make_unique<HttpsUpstream>(this);
+    alpn_ = "http/1.1";
+
+    // At this point, input buffer is already filled with some bytes.
+    // The read callback is not called until new data come. So consume
+    // input buffer here.
+    if (on_read() != 0) {
+      return -1;
+    }
+
+    return 0;
+  }
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "The negotiated protocol is not supported";
+  }
+  return -1;
+}
+
+int ClientHandler::do_read() { return read_(*this); }
+int ClientHandler::do_write() { return write_(*this); }
+
+int ClientHandler::on_read() { return on_read_(*this); }
+int ClientHandler::on_write() { return on_write_(*this); }
+
+const std::string &ClientHandler::get_ipaddr() const { return ipaddr_; }
+
+bool ClientHandler::get_should_close_after_write() const {
+  return should_close_after_write_;
+}
+
+void ClientHandler::set_should_close_after_write(bool f) {
+  should_close_after_write_ = f;
+}
+
+void ClientHandler::pool_downstream_connection(
+    std::unique_ptr<DownstreamConnection> dconn) {
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "Pooling downstream connection DCONN:" << dconn.get();
+  }
+  dconn->set_client_handler(nullptr);
+  dconn_pool_->add_downstream_connection(std::move(dconn));
+}
+
+void ClientHandler::remove_downstream_connection(DownstreamConnection *dconn) {
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "Removing downstream connection DCONN:" << dconn
+                     << " from pool";
+  }
+  dconn_pool_->remove_downstream_connection(dconn);
+}
+
+std::unique_ptr<DownstreamConnection>
+ClientHandler::get_downstream_connection() {
+  auto dconn = dconn_pool_->pop_downstream_connection();
+
+  if (!dconn) {
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, this) << "Downstream connection pool is empty."
+                       << " Create new one";
+    }
+
+    if (http2session_) {
+      dconn =
+          make_unique<Http2DownstreamConnection>(dconn_pool_, http2session_);
+    } else {
+      dconn = make_unique<HttpDownstreamConnection>(dconn_pool_, conn_.loop);
+    }
+    dconn->set_client_handler(this);
+    return dconn;
+  }
+
+  dconn->set_client_handler(this);
+
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, this) << "Reuse downstream connection DCONN:" << dconn.get()
+                     << " from pool";
+  }
+
+  return dconn;
+}
+
+SSL *ClientHandler::get_ssl() const { return conn_.tls.ssl; }
+
+void ClientHandler::set_http2_session(Http2Session *http2session) {
+  http2session_ = http2session;
+}
+
+Http2Session *ClientHandler::get_http2_session() const { return http2session_; }
+
+void ClientHandler::set_http1_connect_blocker(
+    ConnectBlocker *http1_connect_blocker) {
+  http1_connect_blocker_ = http1_connect_blocker;
+}
+
+ConnectBlocker *ClientHandler::get_http1_connect_blocker() const {
+  return http1_connect_blocker_;
+}
+
+void ClientHandler::direct_http2_upgrade() {
+  upstream_ = make_unique<Http2Upstream>(this);
+  // TODO We don't know exact h2 draft version in direct upgrade.  We
+  // just use library default for now.
+  alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
+  on_read_ = &ClientHandler::upstream_read;
+}
+
+int ClientHandler::perform_http2_upgrade(HttpsUpstream *http) {
+  auto upstream = make_unique<Http2Upstream>(this);
+  if (upstream->upgrade_upstream(http) != 0) {
+    return -1;
+  }
+  // http pointer is now owned by upstream.
+  upstream_.release();
+  upstream_ = std::move(upstream);
+  // TODO We might get other version id in HTTP2-settings, if we
+  // support aliasing for h2, but we just use library default for now.
+  alpn_ = NGHTTP2_CLEARTEXT_PROTO_VERSION_ID;
+  on_read_ = &ClientHandler::upstream_http2_connhd_read;
+
+  static char res[] = "HTTP/1.1 101 Switching Protocols\r\n"
+                      "Connection: Upgrade\r\n"
+                      "Upgrade: " NGHTTP2_CLEARTEXT_PROTO_VERSION_ID "\r\n"
+                      "\r\n";
+  wb_.write(res, sizeof(res) - 1);
+  return 0;
+}
+
+bool ClientHandler::get_http2_upgrade_allowed() const { return !conn_.tls.ssl; }
+
+std::string ClientHandler::get_upstream_scheme() const {
+  if (conn_.tls.ssl) {
+    return "https";
+  } else {
+    return "http";
+  }
+}
+
+void ClientHandler::start_immediate_shutdown() {
+  ev_timer_start(conn_.loop, &reneg_shutdown_timer_);
+}
+
+void ClientHandler::write_accesslog(Downstream *downstream) {
+  LogSpec lgsp = {
+      downstream, ipaddr_.c_str(), downstream->get_request_method().c_str(),
+
+      downstream->get_request_path().empty()
+          ? downstream->get_request_http2_authority().c_str()
+          : downstream->get_request_path().c_str(),
+
+      alpn_.c_str(),
+
+      std::chrono::system_clock::now(),          // time_now
+      downstream->get_request_start_time(),      // request_start_time
+      std::chrono::high_resolution_clock::now(), // request_end_time
+
+      downstream->get_request_major(), downstream->get_request_minor(),
+      downstream->get_response_http_status(),
+      downstream->get_response_sent_bodylen(), port_.c_str(),
+      get_config()->port, get_config()->pid,
+  };
+
+  upstream_accesslog(get_config()->accesslog_format, &lgsp);
+}
+
+void ClientHandler::write_accesslog(int major, int minor, unsigned int status,
+                                    int64_t body_bytes_sent) {
+  auto time_now = std::chrono::system_clock::now();
+  auto highres_now = std::chrono::high_resolution_clock::now();
+
+  LogSpec lgsp = {
+      nullptr,            ipaddr_.c_str(),
+      "-", // method
+      "-", // path,
+      alpn_.c_str(),      time_now,
+      highres_now,               // request_start_time TODO is
+                                 // there a better value?
+      highres_now,               // request_end_time
+      major,              minor, // major, minor
+      status,             body_bytes_sent,   port_.c_str(),
+      get_config()->port, get_config()->pid,
+  };
+
+  upstream_accesslog(get_config()->accesslog_format, &lgsp);
+}
+
+WorkerStat *ClientHandler::get_worker_stat() const { return worker_stat_; }
+
+ClientHandler::WriteBuf *ClientHandler::get_wb() { return &wb_; }
+
+ClientHandler::ReadBuf *ClientHandler::get_rb() { return &rb_; }
+
+void ClientHandler::signal_write() { conn_.wlimit.startw(); }
+
+RateLimit *ClientHandler::get_rlimit() { return &conn_.rlimit; }
+RateLimit *ClientHandler::get_wlimit() { return &conn_.wlimit; }
+
+} // namespace shrpx
diff --git a/src/shrpx_client_handler.h b/src/shrpx_client_handler.h
new file mode 100644 (file)
index 0000000..7a6b8f6
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CLIENT_HANDLER_H
+#define SHRPX_CLIENT_HANDLER_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "shrpx_rate_limit.h"
+#include "shrpx_connection.h"
+#include "buffer.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Upstream;
+class DownstreamConnection;
+class Http2Session;
+class HttpsUpstream;
+class ConnectBlocker;
+class DownstreamConnectionPool;
+struct WorkerStat;
+
+class ClientHandler {
+public:
+  ClientHandler(struct ev_loop *loop, int fd, SSL *ssl, const char *ipaddr,
+                const char *port, WorkerStat *worker_stat,
+                DownstreamConnectionPool *dconn_pool);
+  ~ClientHandler();
+
+  // Performs clear text I/O
+  int read_clear();
+  int write_clear();
+  // Performs TLS handshake
+  int tls_handshake();
+  // Performs TLS I/O
+  int read_tls();
+  int write_tls();
+
+  int upstream_noop();
+  int upstream_read();
+  int upstream_http2_connhd_read();
+  int upstream_http1_connhd_read();
+  int upstream_write();
+
+  // Performs I/O operation.  Internally calls on_read()/on_write().
+  int do_read();
+  int do_write();
+
+  // Processes buffers.  No underlying I/O operation will be done.
+  int on_read();
+  int on_write();
+
+  struct ev_loop *get_loop() const;
+  void reset_upstream_read_timeout(ev_tstamp t);
+  void reset_upstream_write_timeout(ev_tstamp t);
+  int validate_next_proto();
+  const std::string &get_ipaddr() const;
+  const std::string &get_port() const;
+  bool get_should_close_after_write() const;
+  void set_should_close_after_write(bool f);
+  Upstream *get_upstream();
+
+  void pool_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+  void remove_downstream_connection(DownstreamConnection *dconn);
+  std::unique_ptr<DownstreamConnection> get_downstream_connection();
+  SSL *get_ssl() const;
+  void set_http2_session(Http2Session *http2session);
+  Http2Session *get_http2_session() const;
+  void set_http1_connect_blocker(ConnectBlocker *http1_connect_blocker);
+  ConnectBlocker *get_http1_connect_blocker() const;
+  // Call this function when HTTP/2 connection header is received at
+  // the start of the connection.
+  void direct_http2_upgrade();
+  // Performs HTTP/2 Upgrade from the connection managed by
+  // |http|. If this function fails, the connection must be
+  // terminated. This function returns 0 if it succeeds, or -1.
+  int perform_http2_upgrade(HttpsUpstream *http);
+  bool get_http2_upgrade_allowed() const;
+  // Returns upstream scheme, either "http" or "https"
+  std::string get_upstream_scheme() const;
+  void start_immediate_shutdown();
+
+  // Writes upstream accesslog using |downstream|.  The |downstream|
+  // must not be nullptr.
+  void write_accesslog(Downstream *downstream);
+
+  // Writes upstream accesslog.  This function is used if
+  // corresponding Downstream object is not available.
+  void write_accesslog(int major, int minor, unsigned int status,
+                       int64_t body_bytes_sent);
+  WorkerStat *get_worker_stat() const;
+
+  using WriteBuf = Buffer<32768>;
+  using ReadBuf = Buffer<8192>;
+
+  WriteBuf *get_wb();
+  ReadBuf *get_rb();
+
+  RateLimit *get_rlimit();
+  RateLimit *get_wlimit();
+
+  void signal_write();
+
+private:
+  Connection conn_;
+  ev_timer reneg_shutdown_timer_;
+  std::unique_ptr<Upstream> upstream_;
+  std::string ipaddr_;
+  std::string port_;
+  // The ALPN identifier negotiated for this connection.
+  std::string alpn_;
+  std::function<int(ClientHandler &)> read_, write_;
+  std::function<int(ClientHandler &)> on_read_, on_write_;
+  DownstreamConnectionPool *dconn_pool_;
+  // Shared HTTP2 session for each thread. NULL if backend is not
+  // HTTP2. Not deleted by this object.
+  Http2Session *http2session_;
+  ConnectBlocker *http1_connect_blocker_;
+  WorkerStat *worker_stat_;
+  // The number of bytes of HTTP/2 client connection header to read
+  size_t left_connhd_len_;
+  bool should_close_after_write_;
+  WriteBuf wb_;
+  ReadBuf rb_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CLIENT_HANDLER_H
diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc
new file mode 100644 (file)
index 0000000..96775a0
--- /dev/null
@@ -0,0 +1,1345 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_config.h"
+
+#include <pwd.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <cerrno>
+#include <limits>
+#include <fstream>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http-parser/http_parser.h"
+
+#include "shrpx_log.h"
+#include "shrpx_ssl.h"
+#include "shrpx_http.h"
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+const char SHRPX_OPT_PRIVATE_KEY_FILE[] = "private-key-file";
+const char SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE[] = "private-key-passwd-file";
+const char SHRPX_OPT_CERTIFICATE_FILE[] = "certificate-file";
+const char SHRPX_OPT_DH_PARAM_FILE[] = "dh-param-file";
+const char SHRPX_OPT_SUBCERT[] = "subcert";
+
+const char SHRPX_OPT_BACKEND[] = "backend";
+const char SHRPX_OPT_FRONTEND[] = "frontend";
+const char SHRPX_OPT_WORKERS[] = "workers";
+const char SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS[] =
+    "http2-max-concurrent-streams";
+const char SHRPX_OPT_LOG_LEVEL[] = "log-level";
+const char SHRPX_OPT_DAEMON[] = "daemon";
+const char SHRPX_OPT_HTTP2_PROXY[] = "http2-proxy";
+const char SHRPX_OPT_HTTP2_BRIDGE[] = "http2-bridge";
+const char SHRPX_OPT_CLIENT_PROXY[] = "client-proxy";
+const char SHRPX_OPT_ADD_X_FORWARDED_FOR[] = "add-x-forwarded-for";
+const char SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR[] =
+    "strip-incoming-x-forwarded-for";
+const char SHRPX_OPT_NO_VIA[] = "no-via";
+const char SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT[] =
+    "frontend-http2-read-timeout";
+const char SHRPX_OPT_FRONTEND_READ_TIMEOUT[] = "frontend-read-timeout";
+const char SHRPX_OPT_FRONTEND_WRITE_TIMEOUT[] = "frontend-write-timeout";
+const char SHRPX_OPT_BACKEND_READ_TIMEOUT[] = "backend-read-timeout";
+const char SHRPX_OPT_BACKEND_WRITE_TIMEOUT[] = "backend-write-timeout";
+const char SHRPX_OPT_STREAM_READ_TIMEOUT[] = "stream-read-timeout";
+const char SHRPX_OPT_STREAM_WRITE_TIMEOUT[] = "stream-write-timeout";
+const char SHRPX_OPT_ACCESSLOG_FILE[] = "accesslog-file";
+const char SHRPX_OPT_ACCESSLOG_SYSLOG[] = "accesslog-syslog";
+const char SHRPX_OPT_ACCESSLOG_FORMAT[] = "accesslog-format";
+const char SHRPX_OPT_ERRORLOG_FILE[] = "errorlog-file";
+const char SHRPX_OPT_ERRORLOG_SYSLOG[] = "errorlog-syslog";
+const char SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT[] =
+    "backend-keep-alive-timeout";
+const char SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS[] =
+    "frontend-http2-window-bits";
+const char SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS[] = "backend-http2-window-bits";
+const char SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS[] =
+    "frontend-http2-connection-window-bits";
+const char SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS[] =
+    "backend-http2-connection-window-bits";
+const char SHRPX_OPT_FRONTEND_NO_TLS[] = "frontend-no-tls";
+const char SHRPX_OPT_BACKEND_NO_TLS[] = "backend-no-tls";
+const char SHRPX_OPT_BACKEND_TLS_SNI_FIELD[] = "backend-tls-sni-field";
+const char SHRPX_OPT_PID_FILE[] = "pid-file";
+const char SHRPX_OPT_USER[] = "user";
+const char SHRPX_OPT_SYSLOG_FACILITY[] = "syslog-facility";
+const char SHRPX_OPT_BACKLOG[] = "backlog";
+const char SHRPX_OPT_CIPHERS[] = "ciphers";
+const char SHRPX_OPT_CLIENT[] = "client";
+const char SHRPX_OPT_INSECURE[] = "insecure";
+const char SHRPX_OPT_CACERT[] = "cacert";
+const char SHRPX_OPT_BACKEND_IPV4[] = "backend-ipv4";
+const char SHRPX_OPT_BACKEND_IPV6[] = "backend-ipv6";
+const char SHRPX_OPT_BACKEND_HTTP_PROXY_URI[] = "backend-http-proxy-uri";
+const char SHRPX_OPT_READ_RATE[] = "read-rate";
+const char SHRPX_OPT_READ_BURST[] = "read-burst";
+const char SHRPX_OPT_WRITE_RATE[] = "write-rate";
+const char SHRPX_OPT_WRITE_BURST[] = "write-burst";
+const char SHRPX_OPT_WORKER_READ_RATE[] = "worker-read-rate";
+const char SHRPX_OPT_WORKER_READ_BURST[] = "worker-read-burst";
+const char SHRPX_OPT_WORKER_WRITE_RATE[] = "worker-write-rate";
+const char SHRPX_OPT_WORKER_WRITE_BURST[] = "worker-write-burst";
+const char SHRPX_OPT_NPN_LIST[] = "npn-list";
+const char SHRPX_OPT_TLS_PROTO_LIST[] = "tls-proto-list";
+const char SHRPX_OPT_VERIFY_CLIENT[] = "verify-client";
+const char SHRPX_OPT_VERIFY_CLIENT_CACERT[] = "verify-client-cacert";
+const char SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE[] = "client-private-key-file";
+const char SHRPX_OPT_CLIENT_CERT_FILE[] = "client-cert-file";
+const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[] =
+    "frontend-http2-dump-request-header";
+const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[] =
+    "frontend-http2-dump-response-header";
+const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[] = "http2-no-cookie-crumbling";
+const char SHRPX_OPT_FRONTEND_FRAME_DEBUG[] = "frontend-frame-debug";
+const char SHRPX_OPT_PADDING[] = "padding";
+const char SHRPX_OPT_ALTSVC[] = "altsvc";
+const char SHRPX_OPT_ADD_RESPONSE_HEADER[] = "add-response-header";
+const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[] =
+    "worker-frontend-connections";
+const char SHRPX_OPT_NO_LOCATION_REWRITE[] = "no-location-rewrite";
+const char SHRPX_OPT_NO_HOST_REWRITE[] = "no-host-rewrite";
+const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[] =
+    "backend-http1-connections-per-host";
+const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[] =
+    "backend-http1-connections-per-frontend";
+const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[] = "listener-disable-timeout";
+const char SHRPX_OPT_TLS_TICKET_KEY_FILE[] = "tls-ticket-key-file";
+const char SHRPX_OPT_RLIMIT_NOFILE[] = "rlimit-nofile";
+const char SHRPX_OPT_TLS_CTX_PER_WORKER[] = "tls-ctx-per-worker";
+const char SHRPX_OPT_BACKEND_REQUEST_BUFFER[] = "backend-request-buffer";
+const char SHRPX_OPT_BACKEND_RESPONSE_BUFFER[] = "backend-response-buffer";
+const char SHRPX_OPT_NO_SERVER_PUSH[] = "no-server-push";
+
+namespace {
+Config *config = nullptr;
+} // namespace
+
+const Config *get_config() { return config; }
+
+Config *mod_config() { return config; }
+
+void create_config() { config = new Config(); }
+
+TicketKeys::~TicketKeys() {
+  /* Erase keys from memory */
+  for (auto &key : keys) {
+    memset(&key, 0, sizeof(key));
+  }
+}
+
+namespace {
+int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
+                    const char *hostport) {
+  // host and port in |hostport| is separated by single ','.
+  const char *p = strchr(hostport, ',');
+  if (!p) {
+    LOG(ERROR) << "Invalid host, port: " << hostport;
+    return -1;
+  }
+  size_t len = p - hostport;
+  if (hostlen < len + 1) {
+    LOG(ERROR) << "Hostname too long: " << hostport;
+    return -1;
+  }
+  memcpy(host, hostport, len);
+  host[len] = '\0';
+
+  errno = 0;
+  unsigned long d = strtoul(p + 1, nullptr, 10);
+  if (errno == 0 && 1 <= d && d <= std::numeric_limits<uint16_t>::max()) {
+    *port_ptr = d;
+    return 0;
+  } else {
+    LOG(ERROR) << "Port is invalid: " << p + 1;
+    return -1;
+  }
+}
+} // namespace
+
+namespace {
+bool is_secure(const char *filename) {
+  struct stat buf;
+  int rv = stat(filename, &buf);
+  if (rv == 0) {
+    if ((buf.st_mode & S_IRWXU) && !(buf.st_mode & S_IRWXG) &&
+        !(buf.st_mode & S_IRWXO)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+} // namespace
+
+std::unique_ptr<TicketKeys>
+read_tls_ticket_key_file(const std::vector<std::string> &files) {
+  auto ticket_keys = make_unique<TicketKeys>();
+  auto &keys = ticket_keys->keys;
+  keys.resize(files.size());
+  size_t i = 0;
+  for (auto &file : files) {
+    std::ifstream f(file.c_str());
+    if (!f) {
+      LOG(ERROR) << "tls-ticket-key-file: could not open file " << file;
+      return nullptr;
+    }
+    char buf[48];
+    f.read(buf, sizeof(buf));
+    if (f.gcount() != sizeof(buf)) {
+      LOG(ERROR) << "tls-ticket-key-file: want to read 48 bytes but read "
+                 << f.gcount() << " bytes from " << file;
+      return nullptr;
+    }
+
+    auto &key = keys[i++];
+    auto p = buf;
+    memcpy(key.name, p, sizeof(key.name));
+    p += sizeof(key.name);
+    memcpy(key.aes_key, p, sizeof(key.aes_key));
+    p += sizeof(key.aes_key);
+    memcpy(key.hmac_key, p, sizeof(key.hmac_key));
+
+    if (LOG_ENABLED(INFO)) {
+      LOG(INFO) << "session ticket key: " << util::format_hex(key.name,
+                                                              sizeof(key.name));
+    }
+  }
+  return ticket_keys;
+}
+
+FILE *open_file_for_write(const char *filename) {
+#if defined O_CLOEXEC
+  auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC,
+                 S_IRUSR | S_IWUSR);
+#else
+  auto fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+
+  // We get race condition if execve is called at the same time.
+  if (fd != -1) {
+    make_socket_closeonexec(fd);
+  }
+#endif
+  if (fd == -1) {
+    LOG(ERROR) << "Failed to open " << filename
+               << " for writing. Cause: " << strerror(errno);
+    return nullptr;
+  }
+  auto f = fdopen(fd, "wb");
+  if (f == nullptr) {
+    LOG(ERROR) << "Failed to open " << filename
+               << " for writing. Cause: " << strerror(errno);
+    return nullptr;
+  }
+
+  return f;
+}
+
+std::string read_passwd_from_file(const char *filename) {
+  std::string line;
+
+  if (!is_secure(filename)) {
+    LOG(ERROR) << "Private key passwd file " << filename
+               << " has insecure mode.";
+    return line;
+  }
+
+  std::ifstream in(filename, std::ios::binary);
+  if (!in) {
+    LOG(ERROR) << "Could not open key passwd file " << filename;
+    return line;
+  }
+
+  std::getline(in, line);
+  return line;
+}
+
+std::unique_ptr<char[]> strcopy(const char *val) {
+  return strcopy(val, strlen(val));
+}
+
+std::unique_ptr<char[]> strcopy(const char *val, size_t len) {
+  auto res = make_unique<char[]>(len + 1);
+  memcpy(res.get(), val, len);
+  res[len] = '\0';
+  return res;
+}
+
+std::unique_ptr<char[]> strcopy(const std::string &val) {
+  return strcopy(val.c_str(), val.size());
+}
+
+std::vector<char *> parse_config_str_list(const char *s) {
+  size_t len = 1;
+  for (const char *first = s, *p = nullptr; (p = strchr(first, ','));
+       ++len, first = p + 1)
+    ;
+  auto list = std::vector<char *>(len);
+  auto first = strdup(s);
+  len = 0;
+  for (;;) {
+    auto p = strchr(first, ',');
+    if (p == nullptr) {
+      break;
+    }
+    list[len++] = first;
+    *p = '\0';
+    first = p + 1;
+  }
+  list[len++] = first;
+
+  return list;
+}
+
+void clear_config_str_list(std::vector<char *> &list) {
+  if (list.empty()) {
+    return;
+  }
+
+  free(list[0]);
+  list.clear();
+}
+
+std::pair<std::string, std::string> parse_header(const char *optarg) {
+  // We skip possible ":" at the start of optarg.
+  const auto *colon = strchr(optarg + 1, ':');
+
+  // name = ":" is not allowed
+  if (colon == nullptr || (optarg[0] == ':' && colon == optarg + 1)) {
+    return {"", ""};
+  }
+
+  auto value = colon + 1;
+  for (; *value == '\t' || *value == ' '; ++value)
+    ;
+
+  return {std::string(optarg, colon), std::string(value, strlen(value))};
+}
+
+template <typename T>
+int parse_uint(T *dest, const char *opt, const char *optarg) {
+  char *end = nullptr;
+
+  errno = 0;
+
+  auto val = strtol(optarg, &end, 10);
+
+  if (!optarg[0] || errno != 0 || *end || val < 0) {
+    LOG(ERROR) << opt << ": bad value.  Specify an integer >= 0.";
+    return -1;
+  }
+
+  *dest = val;
+
+  return 0;
+}
+
+namespace {
+template <typename T>
+int parse_uint_with_unit(T *dest, const char *opt, const char *optarg) {
+  auto n = util::parse_uint_with_unit(optarg);
+  if (n == -1) {
+    LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+    return -1;
+  }
+
+  *dest = n;
+
+  return 0;
+}
+} // namespace
+
+template <typename T>
+int parse_int(T *dest, const char *opt, const char *optarg) {
+  char *end = nullptr;
+
+  errno = 0;
+
+  auto val = strtol(optarg, &end, 10);
+
+  if (!optarg[0] || errno != 0 || *end) {
+    LOG(ERROR) << opt << ": bad value.  Specify an integer.";
+    return -1;
+  }
+
+  *dest = val;
+
+  return 0;
+}
+
+namespace {
+LogFragment make_log_fragment(LogFragmentType type,
+                              std::unique_ptr<char[]> value = nullptr) {
+  return LogFragment{type, std::move(value)};
+}
+} // namespace
+
+namespace {
+bool var_token(char c) {
+  return util::isAlpha(c) || util::isDigit(c) || c == '_';
+}
+} // namespace
+
+std::vector<LogFragment> parse_log_format(const char *optarg) {
+  auto literal_start = optarg;
+  auto p = optarg;
+  auto eop = p + strlen(optarg);
+
+  auto res = std::vector<LogFragment>();
+
+  for (; p != eop;) {
+    if (*p != '$') {
+      ++p;
+      continue;
+    }
+
+    auto var_start = p;
+
+    ++p;
+
+    for (; p != eop && var_token(*p); ++p)
+      ;
+
+    auto varlen = p - var_start;
+
+    auto type = SHRPX_LOGF_NONE;
+    const char *value = nullptr;
+    size_t valuelen = 0;
+
+    if (util::strieq("$remote_addr", var_start, varlen)) {
+      type = SHRPX_LOGF_REMOTE_ADDR;
+    } else if (util::strieq("$time_local", var_start, varlen)) {
+      type = SHRPX_LOGF_TIME_LOCAL;
+    } else if (util::strieq("$time_iso8601", var_start, varlen)) {
+      type = SHRPX_LOGF_TIME_ISO8601;
+    } else if (util::strieq("$request", var_start, varlen)) {
+      type = SHRPX_LOGF_REQUEST;
+    } else if (util::strieq("$status", var_start, varlen)) {
+      type = SHRPX_LOGF_STATUS;
+    } else if (util::strieq("$body_bytes_sent", var_start, varlen)) {
+      type = SHRPX_LOGF_BODY_BYTES_SENT;
+    } else if (util::istartsWith(var_start, varlen, "$http_")) {
+      type = SHRPX_LOGF_HTTP;
+      value = var_start + sizeof("$http_") - 1;
+      valuelen = varlen - (sizeof("$http_") - 1);
+    } else if (util::strieq("$remote_port", var_start, varlen)) {
+      type = SHRPX_LOGF_REMOTE_PORT;
+    } else if (util::strieq("$server_port", var_start, varlen)) {
+      type = SHRPX_LOGF_SERVER_PORT;
+    } else if (util::strieq("$request_time", var_start, varlen)) {
+      type = SHRPX_LOGF_REQUEST_TIME;
+    } else if (util::strieq("$pid", var_start, varlen)) {
+      type = SHRPX_LOGF_PID;
+    } else if (util::strieq("$alpn", var_start, varlen)) {
+      type = SHRPX_LOGF_ALPN;
+    } else {
+      LOG(WARN) << "Unrecognized log format variable: "
+                << std::string(var_start, varlen);
+      continue;
+    }
+
+    if (literal_start < var_start) {
+      res.push_back(
+          make_log_fragment(SHRPX_LOGF_LITERAL,
+                            strcopy(literal_start, var_start - literal_start)));
+    }
+
+    if (value == nullptr) {
+      res.push_back(make_log_fragment(type));
+    } else {
+      res.push_back(make_log_fragment(type, strcopy(value, valuelen)));
+      auto &v = res.back().value;
+      for (size_t i = 0; v[i]; ++i) {
+        if (v[i] == '_') {
+          v[i] = '-';
+        }
+      }
+    }
+
+    literal_start = var_start + varlen;
+  }
+
+  if (literal_start != eop) {
+    res.push_back(make_log_fragment(
+        SHRPX_LOGF_LITERAL, strcopy(literal_start, eop - literal_start)));
+  }
+
+  return res;
+}
+
+namespace {
+int parse_duration(ev_tstamp *dest, const char *opt, const char *optarg) {
+  auto t = util::parse_duration_with_unit(optarg);
+  if (t == std::numeric_limits<double>::infinity()) {
+    LOG(ERROR) << opt << ": bad value: '" << optarg << "'";
+    return -1;
+  }
+
+  *dest = t;
+
+  return 0;
+}
+} // namespace
+
+int parse_config(const char *opt, const char *optarg) {
+  char host[NI_MAXHOST];
+  uint16_t port;
+  if (util::strieq(opt, SHRPX_OPT_BACKEND)) {
+    if (split_host_port(host, sizeof(host), &port, optarg) == -1) {
+      return -1;
+    }
+
+    DownstreamAddr addr;
+    addr.host = strcopy(host);
+    addr.port = port;
+
+    mod_config()->downstream_addrs.push_back(std::move(addr));
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND)) {
+    if (split_host_port(host, sizeof(host), &port, optarg) == -1) {
+      return -1;
+    }
+
+    mod_config()->host = strcopy(host);
+    mod_config()->port = port;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WORKERS)) {
+    return parse_uint(&mod_config()->num_worker, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS)) {
+    return parse_uint(&mod_config()->http2_max_concurrent_streams, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_LOG_LEVEL)) {
+    if (Log::set_severity_level_by_name(optarg) == -1) {
+      LOG(ERROR) << opt << ": Invalid severity level: " << optarg;
+      return -1;
+    }
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_DAEMON)) {
+    mod_config()->daemon = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_HTTP2_PROXY)) {
+    mod_config()->http2_proxy = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_HTTP2_BRIDGE)) {
+    mod_config()->http2_bridge = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CLIENT_PROXY)) {
+    mod_config()->client_proxy = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ADD_X_FORWARDED_FOR)) {
+    mod_config()->add_x_forwarded_for = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR)) {
+    mod_config()->strip_incoming_x_forwarded_for = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_NO_VIA)) {
+    mod_config()->no_via = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT)) {
+    return parse_duration(&mod_config()->http2_upstream_read_timeout, opt,
+                          optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_READ_TIMEOUT)) {
+    return parse_duration(&mod_config()->upstream_read_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_WRITE_TIMEOUT)) {
+    return parse_duration(&mod_config()->upstream_write_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_READ_TIMEOUT)) {
+    return parse_duration(&mod_config()->downstream_read_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_WRITE_TIMEOUT)) {
+    return parse_duration(&mod_config()->downstream_write_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_STREAM_READ_TIMEOUT)) {
+    return parse_duration(&mod_config()->stream_read_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_STREAM_WRITE_TIMEOUT)) {
+    return parse_duration(&mod_config()->stream_write_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ACCESSLOG_FILE)) {
+    mod_config()->accesslog_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ACCESSLOG_SYSLOG)) {
+    mod_config()->accesslog_syslog = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ACCESSLOG_FORMAT)) {
+    mod_config()->accesslog_format = parse_log_format(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ERRORLOG_FILE)) {
+    mod_config()->errorlog_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ERRORLOG_SYSLOG)) {
+    mod_config()->errorlog_syslog = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT)) {
+    return parse_duration(&mod_config()->downstream_idle_read_timeout, opt,
+                          optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS) ||
+      util::strieq(opt, SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS)) {
+
+    size_t *resp;
+
+    if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS)) {
+      resp = &mod_config()->http2_upstream_window_bits;
+    } else {
+      resp = &mod_config()->http2_downstream_window_bits;
+    }
+
+    errno = 0;
+
+    int n;
+
+    if (parse_uint(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n >= 31) {
+      LOG(ERROR) << opt
+                 << ": specify the integer in the range [0, 30], inclusive";
+      return -1;
+    }
+
+    *resp = n;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) ||
+      util::strieq(opt, SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS)) {
+
+    size_t *resp;
+
+    if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS)) {
+      resp = &mod_config()->http2_upstream_connection_window_bits;
+    } else {
+      resp = &mod_config()->http2_downstream_connection_window_bits;
+    }
+
+    errno = 0;
+
+    int n;
+
+    if (parse_uint(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n < 16 || n >= 31) {
+      LOG(ERROR) << opt
+                 << ": specify the integer in the range [16, 30], inclusive";
+      return -1;
+    }
+
+    *resp = n;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_NO_TLS)) {
+    mod_config()->upstream_no_tls = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_NO_TLS)) {
+    mod_config()->downstream_no_tls = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_TLS_SNI_FIELD)) {
+    mod_config()->backend_tls_sni_name = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_PID_FILE)) {
+    mod_config()->pid_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_USER)) {
+    auto pwd = getpwnam(optarg);
+    if (!pwd) {
+      LOG(ERROR) << opt << ": failed to get uid from " << optarg << ": "
+                 << strerror(errno);
+      return -1;
+    }
+    mod_config()->user = strcopy(pwd->pw_name);
+    mod_config()->uid = pwd->pw_uid;
+    mod_config()->gid = pwd->pw_gid;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_PRIVATE_KEY_FILE)) {
+    mod_config()->private_key_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE)) {
+    auto passwd = read_passwd_from_file(optarg);
+    if (passwd.empty()) {
+      LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg;
+      return -1;
+    }
+    mod_config()->private_key_passwd = strcopy(passwd);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CERTIFICATE_FILE)) {
+    mod_config()->cert_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_DH_PARAM_FILE)) {
+    mod_config()->dh_param_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_SUBCERT)) {
+    // Private Key file and certificate file separated by ':'.
+    const char *sp = strchr(optarg, ':');
+    if (sp) {
+      std::string keyfile(optarg, sp);
+      // TODO Do we need private key for subcert?
+      mod_config()->subcerts.emplace_back(keyfile, sp + 1);
+    }
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_SYSLOG_FACILITY)) {
+    int facility = int_syslog_facility(optarg);
+    if (facility == -1) {
+      LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg;
+      return -1;
+    }
+    mod_config()->syslog_facility = facility;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKLOG)) {
+    int n;
+    if (parse_int(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n < -1) {
+      LOG(ERROR) << opt << ": " << optarg << " is not allowed";
+
+      return -1;
+    }
+
+    mod_config()->backlog = n;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CIPHERS)) {
+    mod_config()->ciphers = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CLIENT)) {
+    mod_config()->client = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_INSECURE)) {
+    mod_config()->insecure = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CACERT)) {
+    mod_config()->cacert = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_IPV4)) {
+    mod_config()->backend_ipv4 = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_IPV6)) {
+    mod_config()->backend_ipv6 = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_HTTP_PROXY_URI)) {
+    // parse URI and get hostname, port and optionally userinfo.
+    http_parser_url u;
+    memset(&u, 0, sizeof(u));
+    int rv = http_parser_parse_url(optarg, strlen(optarg), 0, &u);
+    if (rv == 0) {
+      std::string val;
+      if (u.field_set & UF_USERINFO) {
+        http2::copy_url_component(val, &u, UF_USERINFO, optarg);
+        // Surprisingly, u.field_set & UF_USERINFO is nonzero even if
+        // userinfo component is empty string.
+        if (!val.empty()) {
+          val = util::percentDecode(val.begin(), val.end());
+          mod_config()->downstream_http_proxy_userinfo = strcopy(val);
+        }
+      }
+      if (u.field_set & UF_HOST) {
+        http2::copy_url_component(val, &u, UF_HOST, optarg);
+        mod_config()->downstream_http_proxy_host = strcopy(val);
+      } else {
+        LOG(ERROR) << opt << ": no hostname specified";
+        return -1;
+      }
+      if (u.field_set & UF_PORT) {
+        mod_config()->downstream_http_proxy_port = u.port;
+      } else {
+        LOG(ERROR) << opt << ": no port specified";
+        return -1;
+      }
+    } else {
+      LOG(ERROR) << opt << ": parse error";
+      return -1;
+    }
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_READ_RATE)) {
+    return parse_uint_with_unit(&mod_config()->read_rate, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_READ_BURST)) {
+    return parse_uint_with_unit(&mod_config()->read_burst, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WRITE_RATE)) {
+    return parse_uint_with_unit(&mod_config()->write_rate, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WRITE_BURST)) {
+    return parse_uint_with_unit(&mod_config()->write_burst, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WORKER_READ_RATE)) {
+    LOG(WARN) << opt << ": not implemented yet";
+    return parse_uint_with_unit(&mod_config()->worker_read_rate, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WORKER_READ_BURST)) {
+    LOG(WARN) << opt << ": not implemented yet";
+    return parse_uint_with_unit(&mod_config()->worker_read_burst, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_RATE)) {
+    LOG(WARN) << opt << ": not implemented yet";
+    return parse_uint_with_unit(&mod_config()->worker_write_rate, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WORKER_WRITE_BURST)) {
+    LOG(WARN) << opt << ": not implemented yet";
+    return parse_uint_with_unit(&mod_config()->worker_write_burst, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_NPN_LIST)) {
+    clear_config_str_list(mod_config()->npn_list);
+
+    mod_config()->npn_list = parse_config_str_list(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_TLS_PROTO_LIST)) {
+    clear_config_str_list(mod_config()->tls_proto_list);
+
+    mod_config()->tls_proto_list = parse_config_str_list(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_VERIFY_CLIENT)) {
+    mod_config()->verify_client = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_VERIFY_CLIENT_CACERT)) {
+    mod_config()->verify_client_cacert = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE)) {
+    mod_config()->client_private_key_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_CLIENT_CERT_FILE)) {
+    mod_config()->client_cert_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER)) {
+    mod_config()->http2_upstream_dump_request_header_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER)) {
+    mod_config()->http2_upstream_dump_response_header_file = strcopy(optarg);
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING)) {
+    mod_config()->http2_no_cookie_crumbling = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_FRONTEND_FRAME_DEBUG)) {
+    mod_config()->upstream_frame_debug = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_PADDING)) {
+    return parse_uint(&mod_config()->padding, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ALTSVC)) {
+    auto tokens = parse_config_str_list(optarg);
+
+    if (tokens.size() < 2) {
+      // Requires at least protocol_id and port
+      LOG(ERROR) << opt << ": too few parameters: " << optarg;
+      return -1;
+    }
+
+    if (tokens.size() > 4) {
+      // We only need protocol_id, port, host and origin
+      LOG(ERROR) << opt << ": too many parameters: " << optarg;
+      return -1;
+    }
+
+    int port;
+
+    if (parse_uint(&port, opt, tokens[1]) != 0) {
+      return -1;
+    }
+
+    if (port < 1 ||
+        port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
+      LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
+      return -1;
+    }
+
+    AltSvc altsvc;
+
+    altsvc.port = port;
+
+    altsvc.protocol_id = tokens[0];
+    altsvc.protocol_id_len = strlen(altsvc.protocol_id);
+
+    if (tokens.size() > 2) {
+      altsvc.host = tokens[2];
+      altsvc.host_len = strlen(altsvc.host);
+
+      if (tokens.size() > 3) {
+        altsvc.origin = tokens[3];
+        altsvc.origin_len = strlen(altsvc.origin);
+      }
+    }
+
+    mod_config()->altsvcs.push_back(std::move(altsvc));
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_ADD_RESPONSE_HEADER)) {
+    auto p = parse_header(optarg);
+    if (p.first.empty()) {
+      LOG(ERROR) << opt << ": header field name is empty: " << optarg;
+      return -1;
+    }
+    mod_config()->add_response_headers.push_back(std::move(p));
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS)) {
+    return parse_uint(&mod_config()->worker_frontend_connections, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_NO_LOCATION_REWRITE)) {
+    mod_config()->no_location_rewrite = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_NO_HOST_REWRITE)) {
+    mod_config()->no_host_rewrite = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST)) {
+    int n;
+
+    if (parse_uint(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n == 0) {
+      LOG(ERROR) << opt << ": specify an integer strictly more than 0";
+
+      return -1;
+    }
+
+    mod_config()->downstream_connections_per_host = n;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND)) {
+    return parse_uint(&mod_config()->downstream_connections_per_frontend, opt,
+                      optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_LISTENER_DISABLE_TIMEOUT)) {
+    return parse_duration(&mod_config()->listener_disable_timeout, opt, optarg);
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_TLS_TICKET_KEY_FILE)) {
+    mod_config()->tls_ticket_key_files.push_back(optarg);
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_RLIMIT_NOFILE)) {
+    int n;
+
+    if (parse_uint(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n < 0) {
+      LOG(ERROR) << opt << ": specify the integer more than or equal to 0";
+
+      return -1;
+    }
+
+    mod_config()->rlimit_nofile = n;
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_BACKEND_REQUEST_BUFFER) ||
+      util::strieq(opt, SHRPX_OPT_BACKEND_RESPONSE_BUFFER)) {
+    size_t n;
+    if (parse_uint_with_unit(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n == 0) {
+      LOG(ERROR) << opt << ": specify an integer strictly more than 0";
+
+      return -1;
+    }
+
+    if (util::strieq(opt, SHRPX_OPT_BACKEND_REQUEST_BUFFER)) {
+      mod_config()->downstream_request_buffer_size = n;
+    } else {
+      mod_config()->downstream_response_buffer_size = n;
+    }
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_TLS_CTX_PER_WORKER)) {
+    mod_config()->tls_ctx_per_worker = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, SHRPX_OPT_NO_SERVER_PUSH)) {
+    mod_config()->no_server_push = util::strieq(optarg, "yes");
+
+    return 0;
+  }
+
+  if (util::strieq(opt, "conf")) {
+    LOG(WARN) << "conf: ignored";
+
+    return 0;
+  }
+
+  LOG(ERROR) << "Unknown option: " << opt;
+
+  return -1;
+}
+
+int load_config(const char *filename) {
+  std::ifstream in(filename, std::ios::binary);
+  if (!in) {
+    LOG(ERROR) << "Could not open config file " << filename;
+    return -1;
+  }
+  std::string line;
+  int linenum = 0;
+  while (std::getline(in, line)) {
+    ++linenum;
+    if (line.empty() || line[0] == '#') {
+      continue;
+    }
+    size_t i;
+    size_t size = line.size();
+    for (i = 0; i < size && line[i] != '='; ++i)
+      ;
+    if (i == size) {
+      LOG(ERROR) << "Bad configuration format at line " << linenum;
+      return -1;
+    }
+    line[i] = '\0';
+    auto s = line.c_str();
+    if (parse_config(s, s + i + 1) == -1) {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+const char *str_syslog_facility(int facility) {
+  switch (facility) {
+  case (LOG_AUTH):
+    return "auth";
+  case (LOG_AUTHPRIV):
+    return "authpriv";
+  case (LOG_CRON):
+    return "cron";
+  case (LOG_DAEMON):
+    return "daemon";
+  case (LOG_FTP):
+    return "ftp";
+  case (LOG_KERN):
+    return "kern";
+  case (LOG_LOCAL0):
+    return "local0";
+  case (LOG_LOCAL1):
+    return "local1";
+  case (LOG_LOCAL2):
+    return "local2";
+  case (LOG_LOCAL3):
+    return "local3";
+  case (LOG_LOCAL4):
+    return "local4";
+  case (LOG_LOCAL5):
+    return "local5";
+  case (LOG_LOCAL6):
+    return "local6";
+  case (LOG_LOCAL7):
+    return "local7";
+  case (LOG_LPR):
+    return "lpr";
+  case (LOG_MAIL):
+    return "mail";
+  case (LOG_SYSLOG):
+    return "syslog";
+  case (LOG_USER):
+    return "user";
+  case (LOG_UUCP):
+    return "uucp";
+  default:
+    return "(unknown)";
+  }
+}
+
+int int_syslog_facility(const char *strfacility) {
+  if (util::strieq(strfacility, "auth")) {
+    return LOG_AUTH;
+  }
+
+  if (util::strieq(strfacility, "authpriv")) {
+    return LOG_AUTHPRIV;
+  }
+
+  if (util::strieq(strfacility, "cron")) {
+    return LOG_CRON;
+  }
+
+  if (util::strieq(strfacility, "daemon")) {
+    return LOG_DAEMON;
+  }
+
+  if (util::strieq(strfacility, "ftp")) {
+    return LOG_FTP;
+  }
+
+  if (util::strieq(strfacility, "kern")) {
+    return LOG_KERN;
+  }
+
+  if (util::strieq(strfacility, "local0")) {
+    return LOG_LOCAL0;
+  }
+
+  if (util::strieq(strfacility, "local1")) {
+    return LOG_LOCAL1;
+  }
+
+  if (util::strieq(strfacility, "local2")) {
+    return LOG_LOCAL2;
+  }
+
+  if (util::strieq(strfacility, "local3")) {
+    return LOG_LOCAL3;
+  }
+
+  if (util::strieq(strfacility, "local4")) {
+    return LOG_LOCAL4;
+  }
+
+  if (util::strieq(strfacility, "local5")) {
+    return LOG_LOCAL5;
+  }
+
+  if (util::strieq(strfacility, "local6")) {
+    return LOG_LOCAL6;
+  }
+
+  if (util::strieq(strfacility, "local7")) {
+    return LOG_LOCAL7;
+  }
+
+  if (util::strieq(strfacility, "lpr")) {
+    return LOG_LPR;
+  }
+
+  if (util::strieq(strfacility, "mail")) {
+    return LOG_MAIL;
+  }
+
+  if (util::strieq(strfacility, "news")) {
+    return LOG_NEWS;
+  }
+
+  if (util::strieq(strfacility, "syslog")) {
+    return LOG_SYSLOG;
+  }
+
+  if (util::strieq(strfacility, "user")) {
+    return LOG_USER;
+  }
+
+  if (util::strieq(strfacility, "uucp")) {
+    return LOG_UUCP;
+  }
+
+  return -1;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_config.h b/src/shrpx_config.h
new file mode 100644 (file)
index 0000000..ac920b4
--- /dev/null
@@ -0,0 +1,377 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONFIG_H
+#define SHRPX_CONFIG_H
+
+#include "shrpx.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <cstdio>
+#include <vector>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+namespace shrpx {
+
+struct LogFragment;
+
+namespace ssl {
+
+class CertLookupTree;
+
+} // namespace ssl
+
+extern const char SHRPX_OPT_PRIVATE_KEY_FILE[];
+extern const char SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE[];
+extern const char SHRPX_OPT_CERTIFICATE_FILE[];
+extern const char SHRPX_OPT_DH_PARAM_FILE[];
+extern const char SHRPX_OPT_SUBCERT[];
+extern const char SHRPX_OPT_BACKEND[];
+extern const char SHRPX_OPT_FRONTEND[];
+extern const char SHRPX_OPT_WORKERS[];
+extern const char SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS[];
+extern const char SHRPX_OPT_LOG_LEVEL[];
+extern const char SHRPX_OPT_DAEMON[];
+extern const char SHRPX_OPT_HTTP2_PROXY[];
+extern const char SHRPX_OPT_HTTP2_BRIDGE[];
+extern const char SHRPX_OPT_CLIENT_PROXY[];
+extern const char SHRPX_OPT_ADD_X_FORWARDED_FOR[];
+extern const char SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR[];
+extern const char SHRPX_OPT_NO_VIA[];
+extern const char SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT[];
+extern const char SHRPX_OPT_FRONTEND_READ_TIMEOUT[];
+extern const char SHRPX_OPT_FRONTEND_WRITE_TIMEOUT[];
+extern const char SHRPX_OPT_BACKEND_READ_TIMEOUT[];
+extern const char SHRPX_OPT_BACKEND_WRITE_TIMEOUT[];
+extern const char SHRPX_OPT_STREAM_READ_TIMEOUT[];
+extern const char SHRPX_OPT_STREAM_WRITE_TIMEOUT[];
+extern const char SHRPX_OPT_ACCESSLOG_FILE[];
+extern const char SHRPX_OPT_ACCESSLOG_SYSLOG[];
+extern const char SHRPX_OPT_ACCESSLOG_FORMAT[];
+extern const char SHRPX_OPT_ERRORLOG_FILE[];
+extern const char SHRPX_OPT_ERRORLOG_SYSLOG[];
+extern const char SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT[];
+extern const char SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS[];
+extern const char SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS[];
+extern const char SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS[];
+extern const char SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS[];
+extern const char SHRPX_OPT_FRONTEND_NO_TLS[];
+extern const char SHRPX_OPT_BACKEND_NO_TLS[];
+extern const char SHRPX_OPT_PID_FILE[];
+extern const char SHRPX_OPT_USER[];
+extern const char SHRPX_OPT_SYSLOG_FACILITY[];
+extern const char SHRPX_OPT_BACKLOG[];
+extern const char SHRPX_OPT_CIPHERS[];
+extern const char SHRPX_OPT_CLIENT[];
+extern const char SHRPX_OPT_INSECURE[];
+extern const char SHRPX_OPT_CACERT[];
+extern const char SHRPX_OPT_BACKEND_IPV4[];
+extern const char SHRPX_OPT_BACKEND_IPV6[];
+extern const char SHRPX_OPT_BACKEND_HTTP_PROXY_URI[];
+extern const char SHRPX_OPT_BACKEND_TLS_SNI_FIELD[];
+extern const char SHRPX_OPT_READ_RATE[];
+extern const char SHRPX_OPT_READ_BURST[];
+extern const char SHRPX_OPT_WRITE_RATE[];
+extern const char SHRPX_OPT_WRITE_BURST[];
+extern const char SHRPX_OPT_WORKER_READ_RATE[];
+extern const char SHRPX_OPT_WORKER_READ_BURST[];
+extern const char SHRPX_OPT_WORKER_WRITE_RATE[];
+extern const char SHRPX_OPT_WORKER_WRITE_BURST[];
+extern const char SHRPX_OPT_NPN_LIST[];
+extern const char SHRPX_OPT_TLS_PROTO_LIST[];
+extern const char SHRPX_OPT_VERIFY_CLIENT[];
+extern const char SHRPX_OPT_VERIFY_CLIENT_CACERT[];
+extern const char SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE[];
+extern const char SHRPX_OPT_CLIENT_CERT_FILE[];
+extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[];
+extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[];
+extern const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[];
+extern const char SHRPX_OPT_FRONTEND_FRAME_DEBUG[];
+extern const char SHRPX_OPT_PADDING[];
+extern const char SHRPX_OPT_ALTSVC[];
+extern const char SHRPX_OPT_ADD_RESPONSE_HEADER[];
+extern const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[];
+extern const char SHRPX_OPT_NO_LOCATION_REWRITE[];
+extern const char SHRPX_OPT_NO_HOST_REWRITE[];
+extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[];
+extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[];
+extern const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[];
+extern const char SHRPX_OPT_TLS_TICKET_KEY_FILE[];
+extern const char SHRPX_OPT_RLIMIT_NOFILE[];
+extern const char SHRPX_OPT_TLS_CTX_PER_WORKER[];
+extern const char SHRPX_OPT_BACKEND_REQUEST_BUFFER[];
+extern const char SHRPX_OPT_BACKEND_RESPONSE_BUFFER[];
+extern const char SHRPX_OPT_NO_SERVER_PUSH[];
+
+union sockaddr_union {
+  sockaddr_storage storage;
+  sockaddr sa;
+  sockaddr_in6 in6;
+  sockaddr_in in;
+};
+
+enum shrpx_proto { PROTO_HTTP2, PROTO_HTTP };
+
+struct AltSvc {
+  AltSvc()
+      : protocol_id(nullptr), host(nullptr), origin(nullptr),
+        protocol_id_len(0), host_len(0), origin_len(0), port(0) {}
+
+  char *protocol_id;
+  char *host;
+  char *origin;
+
+  size_t protocol_id_len;
+  size_t host_len;
+  size_t origin_len;
+
+  uint16_t port;
+};
+
+struct DownstreamAddr {
+  DownstreamAddr() : addr{{0}}, addrlen(0), port(0) {}
+  sockaddr_union addr;
+  std::unique_ptr<char[]> host;
+  std::unique_ptr<char[]> hostport;
+  size_t addrlen;
+  uint16_t port;
+};
+
+struct TicketKey {
+  uint8_t name[16];
+  uint8_t aes_key[16];
+  uint8_t hmac_key[16];
+};
+
+struct TicketKeys {
+  ~TicketKeys();
+  std::vector<TicketKey> keys;
+};
+
+struct Config {
+  // The list of (private key file, certificate file) pair
+  std::vector<std::pair<std::string, std::string>> subcerts;
+  std::vector<AltSvc> altsvcs;
+  std::vector<std::pair<std::string, std::string>> add_response_headers;
+  std::vector<unsigned char> alpn_prefs;
+  std::vector<LogFragment> accesslog_format;
+  std::vector<DownstreamAddr> downstream_addrs;
+  std::vector<std::string> tls_ticket_key_files;
+  // binary form of http proxy host and port
+  sockaddr_union downstream_http_proxy_addr;
+  ev_tstamp http2_upstream_read_timeout;
+  ev_tstamp upstream_read_timeout;
+  ev_tstamp upstream_write_timeout;
+  ev_tstamp downstream_read_timeout;
+  ev_tstamp downstream_write_timeout;
+  ev_tstamp stream_read_timeout;
+  ev_tstamp stream_write_timeout;
+  ev_tstamp downstream_idle_read_timeout;
+  ev_tstamp listener_disable_timeout;
+  std::unique_ptr<char[]> host;
+  std::unique_ptr<char[]> private_key_file;
+  std::unique_ptr<char[]> private_key_passwd;
+  std::unique_ptr<char[]> cert_file;
+  std::unique_ptr<char[]> dh_param_file;
+  const char *server_name;
+  std::unique_ptr<char[]> backend_tls_sni_name;
+  std::unique_ptr<char[]> pid_file;
+  std::unique_ptr<char[]> conf_path;
+  std::unique_ptr<char[]> ciphers;
+  std::unique_ptr<char[]> cacert;
+  // userinfo in http proxy URI, not percent-encoded form
+  std::unique_ptr<char[]> downstream_http_proxy_userinfo;
+  // host in http proxy URI
+  std::unique_ptr<char[]> downstream_http_proxy_host;
+  std::unique_ptr<char[]> http2_upstream_dump_request_header_file;
+  std::unique_ptr<char[]> http2_upstream_dump_response_header_file;
+  // // Rate limit configuration per connection
+  // ev_token_bucket_cfg *rate_limit_cfg;
+  // // Rate limit configuration per worker (thread)
+  // ev_token_bucket_cfg *worker_rate_limit_cfg;
+  // list of supported NPN/ALPN protocol strings in the order of
+  // preference. The each element of this list is a NULL-terminated
+  // string.
+  std::vector<char *> npn_list;
+  // list of supported SSL/TLS protocol strings. The each element of
+  // this list is a NULL-terminated string.
+  std::vector<char *> tls_proto_list;
+  // Path to file containing CA certificate solely used for client
+  // certificate validation
+  std::unique_ptr<char[]> verify_client_cacert;
+  std::unique_ptr<char[]> client_private_key_file;
+  std::unique_ptr<char[]> client_cert_file;
+  std::unique_ptr<char[]> accesslog_file;
+  std::unique_ptr<char[]> errorlog_file;
+  FILE *http2_upstream_dump_request_header;
+  FILE *http2_upstream_dump_response_header;
+  nghttp2_option *http2_option;
+  nghttp2_option *http2_client_option;
+  char **argv;
+  char *cwd;
+  size_t num_worker;
+  size_t http2_max_concurrent_streams;
+  size_t http2_upstream_window_bits;
+  size_t http2_downstream_window_bits;
+  size_t http2_upstream_connection_window_bits;
+  size_t http2_downstream_connection_window_bits;
+  size_t downstream_connections_per_host;
+  size_t downstream_connections_per_frontend;
+  // actual size of downstream_http_proxy_addr
+  size_t downstream_http_proxy_addrlen;
+  size_t read_rate;
+  size_t read_burst;
+  size_t write_rate;
+  size_t write_burst;
+  size_t worker_read_rate;
+  size_t worker_read_burst;
+  size_t worker_write_rate;
+  size_t worker_write_burst;
+  size_t padding;
+  size_t worker_frontend_connections;
+  size_t rlimit_nofile;
+  size_t downstream_request_buffer_size;
+  size_t downstream_response_buffer_size;
+  // Bit mask to disable SSL/TLS protocol versions.  This will be
+  // passed to SSL_CTX_set_options().
+  long int tls_proto_mask;
+  // downstream protocol; this will be determined by given options.
+  shrpx_proto downstream_proto;
+  int syslog_facility;
+  int backlog;
+  int argc;
+  std::unique_ptr<char[]> user;
+  uid_t uid;
+  gid_t gid;
+  pid_t pid;
+  uint16_t port;
+  // port in http proxy URI
+  uint16_t downstream_http_proxy_port;
+  bool verbose;
+  bool daemon;
+  bool verify_client;
+  bool http2_proxy;
+  bool http2_bridge;
+  bool client_proxy;
+  bool add_x_forwarded_for;
+  bool strip_incoming_x_forwarded_for;
+  bool no_via;
+  bool upstream_no_tls;
+  bool downstream_no_tls;
+  // Send accesslog to syslog, ignoring accesslog_file.
+  bool accesslog_syslog;
+  // Send errorlog to syslog, ignoring errorlog_file.
+  bool errorlog_syslog;
+  bool client;
+  // true if --client or --client-proxy are enabled.
+  bool client_mode;
+  bool insecure;
+  bool backend_ipv4;
+  bool backend_ipv6;
+  bool http2_no_cookie_crumbling;
+  bool upstream_frame_debug;
+  bool no_location_rewrite;
+  bool no_host_rewrite;
+  bool auto_tls_ticket_key;
+  bool tls_ctx_per_worker;
+  bool no_server_push;
+};
+
+const Config *get_config();
+Config *mod_config();
+void create_config();
+
+// Parses option name |opt| and value |optarg|.  The results are
+// stored into statically allocated Config object. This function
+// returns 0 if it succeeds, or -1.
+int parse_config(const char *opt, const char *optarg);
+
+// Loads configurations from |filename| and stores them in statically
+// allocated Config object. This function returns 0 if it succeeds, or
+// -1.
+int load_config(const char *filename);
+
+// Read passwd from |filename|
+std::string read_passwd_from_file(const char *filename);
+
+// Parses comma delimited strings in |s| and returns the array of
+// pointers, each element points to the each substring in |s|.  The
+// |s| must be comma delimited list of strings.  The strings must be
+// delimited by a single comma and any white spaces around it are
+// treated as a part of protocol strings.  This function may modify
+// |s| and the caller must leave it as is after this call.  This
+// function copies |s| and first element in the return value points to
+// it.  It is caller's responsibility to deallocate its memory.
+std::vector<char *> parse_config_str_list(const char *s);
+
+// Clears all elements of |list|, which is returned by
+// parse_config_str_list().  If list is not empty, list[0] is freed by
+// free(2).  After this call, list.empty() must be true.
+void clear_config_str_list(std::vector<char *> &list);
+
+// Parses header field in |optarg|.  We expect header field is formed
+// like "NAME: VALUE".  We require that NAME is non empty string.  ":"
+// is allowed at the start of the NAME, but NAME == ":" is not
+// allowed.  This function returns pair of NAME and VALUE.
+std::pair<std::string, std::string> parse_header(const char *optarg);
+
+std::vector<LogFragment> parse_log_format(const char *optarg);
+
+// Returns a copy of NULL-terminated string |val|.
+std::unique_ptr<char[]> strcopy(const char *val);
+
+// Returns a copy of string |val| of length |n|.  The returned string
+// will be NULL-terminated.
+std::unique_ptr<char[]> strcopy(const char *val, size_t n);
+
+// Returns a copy of val.c_str().
+std::unique_ptr<char[]> strcopy(const std::string &val);
+
+// Returns string for syslog |facility|.
+const char *str_syslog_facility(int facility);
+
+// Returns integer value of syslog |facility| string.
+int int_syslog_facility(const char *strfacility);
+
+FILE *open_file_for_write(const char *filename);
+
+// Reads TLS ticket key file in |files| and returns TicketKey which
+// stores read key data.  This function returns TicketKey if it
+// succeeds, or nullptr.
+std::unique_ptr<TicketKeys>
+read_tls_ticket_key_file(const std::vector<std::string> &files);
+
+} // namespace shrpx
+
+#endif // SHRPX_CONFIG_H
diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc
new file mode 100644 (file)
index 0000000..e326b4a
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_config_test.h"
+
+#include <unistd.h>
+
+#include <cstdlib>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_config.h"
+
+namespace shrpx {
+
+void test_shrpx_config_parse_config_str_list(void) {
+  auto res = parse_config_str_list("a");
+  CU_ASSERT(1 == res.size());
+  CU_ASSERT(0 == strcmp("a", res[0]));
+  clear_config_str_list(res);
+
+  res = parse_config_str_list("a,");
+  CU_ASSERT(2 == res.size());
+  CU_ASSERT(0 == strcmp("a", res[0]));
+  CU_ASSERT(0 == strcmp("", res[1]));
+  clear_config_str_list(res);
+
+  res = parse_config_str_list(",a,,");
+  CU_ASSERT(4 == res.size());
+  CU_ASSERT(0 == strcmp("", res[0]));
+  CU_ASSERT(0 == strcmp("a", res[1]));
+  CU_ASSERT(0 == strcmp("", res[2]));
+  CU_ASSERT(0 == strcmp("", res[3]));
+  clear_config_str_list(res);
+
+  res = parse_config_str_list("");
+  CU_ASSERT(1 == res.size());
+  CU_ASSERT(0 == strcmp("", res[0]));
+  clear_config_str_list(res);
+
+  res = parse_config_str_list("alpha,bravo,charlie");
+  CU_ASSERT(3 == res.size());
+  CU_ASSERT(0 == strcmp("alpha", res[0]));
+  CU_ASSERT(0 == strcmp("bravo", res[1]));
+  CU_ASSERT(0 == strcmp("charlie", res[2]));
+  clear_config_str_list(res);
+}
+
+void test_shrpx_config_parse_header(void) {
+  auto p = parse_header("a: b");
+  CU_ASSERT("a" == p.first);
+  CU_ASSERT("b" == p.second);
+
+  p = parse_header("a:  b");
+  CU_ASSERT("a" == p.first);
+  CU_ASSERT("b" == p.second);
+
+  p = parse_header(":a: b");
+  CU_ASSERT(":a" == p.first);
+  CU_ASSERT("b" == p.second);
+
+  p = parse_header("a: :b");
+  CU_ASSERT("a" == p.first);
+  CU_ASSERT(":b" == p.second);
+
+  p = parse_header(": b");
+  CU_ASSERT(p.first.empty());
+
+  p = parse_header("alpha: bravo charlie");
+  CU_ASSERT("alpha" == p.first);
+  CU_ASSERT("bravo charlie" == p.second);
+}
+
+void test_shrpx_config_parse_log_format(void) {
+  auto res = parse_log_format("$remote_addr - $remote_user [$time_local] "
+                              "\"$request\" $status $body_bytes_sent "
+                              "\"$http_referer\" \"$http_user_agent\"");
+  CU_ASSERT(14 == res.size());
+
+  CU_ASSERT(SHRPX_LOGF_REMOTE_ADDR == res[0].type);
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[1].type);
+  CU_ASSERT(0 == strcmp(" - $remote_user [", res[1].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_TIME_LOCAL == res[2].type);
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[3].type);
+  CU_ASSERT(0 == strcmp("] \"", res[3].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_REQUEST == res[4].type);
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[5].type);
+  CU_ASSERT(0 == strcmp("\" ", res[5].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_STATUS == res[6].type);
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[7].type);
+  CU_ASSERT(0 == strcmp(" ", res[7].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_BODY_BYTES_SENT == res[8].type);
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[9].type);
+  CU_ASSERT(0 == strcmp(" \"", res[9].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_HTTP == res[10].type);
+  CU_ASSERT(0 == strcmp("referer", res[10].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[11].type);
+  CU_ASSERT(0 == strcmp("\" \"", res[11].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_HTTP == res[12].type);
+  CU_ASSERT(0 == strcmp("user-agent", res[12].value.get()));
+
+  CU_ASSERT(SHRPX_LOGF_LITERAL == res[13].type);
+  CU_ASSERT(0 == strcmp("\"", res[13].value.get()));
+}
+
+void test_shrpx_config_read_tls_ticket_key_file(void) {
+  char file1[] = "/tmp/nghttpx-unittest.XXXXXX";
+  auto fd1 = mkstemp(file1);
+  assert(fd1 != -1);
+  assert(48 ==
+         write(fd1, "0..............12..............34..............5", 48));
+  char file2[] = "/tmp/nghttpx-unittest.XXXXXX";
+  auto fd2 = mkstemp(file2);
+  assert(fd2 != -1);
+  assert(48 ==
+         write(fd2, "6..............78..............9a..............b", 48));
+
+  close(fd1);
+  close(fd2);
+  auto ticket_keys = read_tls_ticket_key_file({file1, file2});
+  unlink(file1);
+  unlink(file2);
+  CU_ASSERT(ticket_keys.get() != nullptr);
+  CU_ASSERT(2 == ticket_keys->keys.size());
+  auto key = &ticket_keys->keys[0];
+  CU_ASSERT(0 == memcmp("0..............1", key->name, sizeof(key->name)));
+  CU_ASSERT(0 ==
+            memcmp("2..............3", key->aes_key, sizeof(key->aes_key)));
+  CU_ASSERT(0 ==
+            memcmp("4..............5", key->hmac_key, sizeof(key->hmac_key)));
+
+  key = &ticket_keys->keys[1];
+  CU_ASSERT(0 == memcmp("6..............7", key->name, sizeof(key->name)));
+  CU_ASSERT(0 ==
+            memcmp("8..............9", key->aes_key, sizeof(key->aes_key)));
+  CU_ASSERT(0 ==
+            memcmp("a..............b", key->hmac_key, sizeof(key->hmac_key)));
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_config_test.h b/src/shrpx_config_test.h
new file mode 100644 (file)
index 0000000..9db5d4e
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONFIG_TEST_H
+#define SHRPX_CONFIG_TEST_H
+
+namespace shrpx {
+
+void test_shrpx_config_parse_config_str_list(void);
+void test_shrpx_config_parse_header(void);
+void test_shrpx_config_parse_log_format(void);
+void test_shrpx_config_read_tls_ticket_key_file(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_CONFIG_TEST_H
diff --git a/src/shrpx_connect_blocker.cc b/src/shrpx_connect_blocker.cc
new file mode 100644 (file)
index 0000000..3eb6de1
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_connect_blocker.h"
+
+namespace shrpx {
+
+namespace {
+const ev_tstamp INITIAL_SLEEP = 2.;
+} // namespace
+
+namespace {
+void connect_blocker_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "unblock downstream connection";
+  }
+}
+} // namespace
+
+ConnectBlocker::ConnectBlocker(struct ev_loop *loop)
+    : loop_(loop), sleep_(INITIAL_SLEEP) {
+  ev_timer_init(&timer_, connect_blocker_cb, 0., 0.);
+}
+
+ConnectBlocker::~ConnectBlocker() { ev_timer_stop(loop_, &timer_); }
+
+bool ConnectBlocker::blocked() const { return ev_is_active(&timer_); }
+
+void ConnectBlocker::on_success() { sleep_ = INITIAL_SLEEP; }
+
+void ConnectBlocker::on_failure() {
+  if (ev_is_active(&timer_)) {
+    return;
+  }
+
+  sleep_ = std::min(128., sleep_ * 2);
+
+  LOG(WARN) << "connect failure, start sleeping " << sleep_;
+
+  ev_timer_set(&timer_, sleep_, 0.);
+  ev_timer_start(loop_, &timer_);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connect_blocker.h b/src/shrpx_connect_blocker.h
new file mode 100644 (file)
index 0000000..af44564
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONNECT_BLOCKER_H
+#define SHRPX_CONNECT_BLOCKER_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+namespace shrpx {
+
+class ConnectBlocker {
+public:
+  ConnectBlocker(struct ev_loop *loop);
+  ~ConnectBlocker();
+
+  // Returns true if making connection is not allowed.
+  bool blocked() const;
+  // Call this function if connect operation succeeded.  This will
+  // reset sleep_ to minimum value.
+  void on_success();
+  // Call this function if connect operation failed.  This will start
+  // timer and blocks connection establishment for sleep_ seconds.
+  void on_failure();
+
+private:
+  ev_timer timer_;
+  struct ev_loop *loop_;
+  ev_tstamp sleep_;
+};
+
+} // namespace
+
+#endif // SHRPX_CONNECT_BLOCKER_H
diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc
new file mode 100644 (file)
index 0000000..e4863bf
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_connection.h"
+
+#include <unistd.h>
+
+#include <limits>
+
+#include <openssl/err.h>
+
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
+                       ev_tstamp write_timeout, ev_tstamp read_timeout,
+                       size_t write_rate, size_t write_burst, size_t read_rate,
+                       size_t read_burst, IOCb writecb, IOCb readcb,
+                       TimerCb timeoutcb, void *data)
+    : tls{ssl}, wlimit(loop, &wev, write_rate, write_burst),
+      rlimit(loop, &rev, read_rate, read_burst), writecb(writecb),
+      readcb(readcb), timeoutcb(timeoutcb), loop(loop), data(data), fd(fd) {
+
+  ev_io_init(&wev, writecb, fd, EV_WRITE);
+  ev_io_init(&rev, readcb, fd, EV_READ);
+
+  wev.data = this;
+  rev.data = this;
+
+  ev_timer_init(&wt, timeoutcb, 0., write_timeout);
+  ev_timer_init(&rt, timeoutcb, 0., read_timeout);
+
+  wt.data = this;
+  rt.data = this;
+
+  // set 0. to double field explicitly just in case
+  tls.last_write_time = 0.;
+}
+
+Connection::~Connection() { disconnect(); }
+
+void Connection::disconnect() {
+  ev_timer_stop(loop, &rt);
+  ev_timer_stop(loop, &wt);
+
+  rlimit.stopw();
+  wlimit.stopw();
+
+  if (tls.ssl) {
+    SSL_set_app_data(tls.ssl, nullptr);
+    SSL_set_shutdown(tls.ssl, SSL_RECEIVED_SHUTDOWN);
+    ERR_clear_error();
+    SSL_shutdown(tls.ssl);
+    SSL_free(tls.ssl);
+    tls.ssl = nullptr;
+  }
+
+  if (fd != -1) {
+    shutdown(fd, SHUT_WR);
+    close(fd);
+    fd = -1;
+  }
+}
+
+int Connection::tls_handshake() {
+  auto rv = SSL_do_handshake(tls.ssl);
+
+  if (rv == 0) {
+    return SHRPX_ERR_NETWORK;
+  }
+
+  if (rv < 0) {
+    auto err = SSL_get_error(tls.ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      wlimit.stopw();
+      ev_timer_stop(loop, &wt);
+      return SHRPX_ERR_INPROGRESS;
+    case SSL_ERROR_WANT_WRITE:
+      wlimit.startw();
+      ev_timer_again(loop, &wt);
+      return SHRPX_ERR_INPROGRESS;
+    default:
+      return SHRPX_ERR_NETWORK;
+    }
+  }
+
+  wlimit.stopw();
+  ev_timer_stop(loop, &wt);
+
+  tls.initial_handshake_done = true;
+
+  if (LOG_ENABLED(INFO)) {
+    LOG(INFO) << "SSL/TLS handshake completed";
+    if (SSL_session_reused(tls.ssl)) {
+      LOG(INFO) << "SSL/TLS session reused";
+    }
+  }
+
+  return 0;
+}
+
+namespace {
+const size_t SHRPX_SMALL_WRITE_LIMIT = 1300;
+const size_t SHRPX_WARMUP_THRESHOLD = 1 << 20;
+} // namespace
+
+ssize_t Connection::get_tls_write_limit() {
+  auto t = ev_now(loop);
+
+  if (t - tls.last_write_time > 1.) {
+    // Time out, use small record size
+    tls.warmup_writelen = 0;
+    return SHRPX_SMALL_WRITE_LIMIT;
+  }
+
+  if (tls.warmup_writelen >= SHRPX_WARMUP_THRESHOLD) {
+    return std::numeric_limits<ssize_t>::max();
+  }
+
+  return SHRPX_SMALL_WRITE_LIMIT;
+}
+
+void Connection::update_tls_warmup_writelen(size_t n) {
+  if (tls.warmup_writelen < SHRPX_WARMUP_THRESHOLD) {
+    tls.warmup_writelen += n;
+  }
+}
+
+ssize_t Connection::write_tls(const void *data, size_t len) {
+  ssize_t nwrite;
+  // SSL_write requires the same arguments (buf pointer and its
+  // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
+  // get_write_limit() may return smaller length than previously
+  // passed to SSL_write, which violates OpenSSL assumption.  To avoid
+  // this, we keep last legnth passed to SSL_write to
+  // tls.last_writelen if SSL_write indicated I/O blocking.
+  if (tls.last_writelen == 0) {
+    nwrite = std::min(len, wlimit.avail());
+    nwrite = std::min(nwrite, get_tls_write_limit());
+    if (nwrite == 0) {
+      return 0;
+    }
+  } else {
+    nwrite = tls.last_writelen;
+    tls.last_writelen = 0;
+  }
+
+  auto rv = SSL_write(tls.ssl, data, nwrite);
+
+  if (rv == 0) {
+    return SHRPX_ERR_NETWORK;
+  }
+
+  tls.last_write_time = ev_now(loop);
+
+  if (rv < 0) {
+    auto err = SSL_get_error(tls.ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      if (LOG_ENABLED(INFO)) {
+        LOG(INFO) << "Close connection due to TLS renegotiation";
+      }
+      return SHRPX_ERR_NETWORK;
+    case SSL_ERROR_WANT_WRITE:
+      tls.last_writelen = nwrite;
+      wlimit.startw();
+      ev_timer_again(loop, &wt);
+      return 0;
+    default:
+      if (LOG_ENABLED(INFO)) {
+        LOG(INFO) << "SSL_write: SSL_get_error returned " << err;
+      }
+      return SHRPX_ERR_NETWORK;
+    }
+  }
+
+  wlimit.drain(rv);
+
+  update_tls_warmup_writelen(rv);
+
+  return rv;
+}
+
+ssize_t Connection::read_tls(void *data, size_t len) {
+  ssize_t nread;
+  // SSL_read requires the same arguments (buf pointer and its
+  // length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
+  // rlimit_.avail() or rlimit_.avail() may return different length
+  // than the length previously passed to SSL_read, which violates
+  // OpenSSL assumption.  To avoid this, we keep last legnth passed
+  // to SSL_read to tls_last_readlen_ if SSL_read indicated I/O
+  // blocking.
+  if (tls.last_readlen == 0) {
+    nread = std::min(len, rlimit.avail());
+    if (nread == 0) {
+      return 0;
+    }
+  } else {
+    nread = tls.last_readlen;
+    tls.last_readlen = 0;
+  }
+
+  auto rv = SSL_read(tls.ssl, data, nread);
+
+  if (rv <= 0) {
+    auto err = SSL_get_error(tls.ssl, rv);
+    switch (err) {
+    case SSL_ERROR_WANT_READ:
+      tls.last_readlen = nread;
+      return 0;
+    case SSL_ERROR_WANT_WRITE:
+      if (LOG_ENABLED(INFO)) {
+        LOG(INFO) << "Close connection due to TLS renegotiation";
+      }
+      return SHRPX_ERR_NETWORK;
+    case SSL_ERROR_ZERO_RETURN:
+      return SHRPX_ERR_EOF;
+    default:
+      if (LOG_ENABLED(INFO)) {
+        LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
+      }
+      return SHRPX_ERR_NETWORK;
+    }
+  }
+
+  rlimit.drain(rv);
+
+  return rv;
+}
+
+ssize_t Connection::write_clear(const void *data, size_t len) {
+  ssize_t nwrite = std::min(len, wlimit.avail());
+  if (nwrite == 0) {
+    return 0;
+  }
+
+  while ((nwrite = write(fd, data, nwrite)) == -1 && errno == EINTR)
+    ;
+  if (nwrite == -1) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+      wlimit.startw();
+      ev_timer_again(loop, &wt);
+      return 0;
+    }
+    return SHRPX_ERR_NETWORK;
+  }
+
+  wlimit.drain(nwrite);
+
+  return nwrite;
+}
+
+ssize_t Connection::writev_clear(struct iovec *iov, int iovcnt) {
+  iovcnt = limit_iovec(iov, iovcnt, wlimit.avail());
+  if (iovcnt == 0) {
+    return 0;
+  }
+
+  ssize_t nwrite;
+  while ((nwrite = writev(fd, iov, iovcnt)) == -1 && errno == EINTR)
+    ;
+  if (nwrite == -1) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+      wlimit.startw();
+      ev_timer_again(loop, &wt);
+      return 0;
+    }
+    return SHRPX_ERR_NETWORK;
+  }
+
+  wlimit.drain(nwrite);
+
+  return nwrite;
+}
+
+ssize_t Connection::read_clear(void *data, size_t len) {
+  ssize_t nread = std::min(len, rlimit.avail());
+  if (nread == 0) {
+    return 0;
+  }
+
+  while ((nread = read(fd, data, nread)) == -1 && errno == EINTR)
+    ;
+  if (nread == -1) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+      return 0;
+    }
+    return SHRPX_ERR_NETWORK;
+  }
+
+  if (nread == 0) {
+    return SHRPX_ERR_EOF;
+  }
+
+  rlimit.drain(nread);
+
+  return nread;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h
new file mode 100644 (file)
index 0000000..8a7e7be
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONNECTION_H
+#define SHRPX_CONNECTION_H
+
+#include "shrpx_config.h"
+
+#include <sys/uio.h>
+
+#include <ev.h>
+
+#include <openssl/ssl.h>
+
+#include "shrpx_rate_limit.h"
+#include "shrpx_error.h"
+
+namespace shrpx {
+
+struct TLSConnection {
+  SSL *ssl;
+  ev_tstamp last_write_time;
+  size_t warmup_writelen;
+  // length passed to SSL_write and SSL_read last time.  This is
+  // required since these functions require the exact same parameters
+  // on non-blocking I/O.
+  size_t last_writelen, last_readlen;
+  bool initial_handshake_done;
+  bool reneg_started;
+};
+
+template <typename T> using EVCb = void (*)(struct ev_loop *, T *, int);
+
+using IOCb = EVCb<ev_io>;
+using TimerCb = EVCb<ev_timer>;
+
+struct Connection {
+  Connection(struct ev_loop *loop, int fd, SSL *ssl, ev_tstamp write_timeout,
+             ev_tstamp read_timeout, size_t write_rate, size_t write_burst,
+             size_t read_rate, size_t read_burst, IOCb writecb, IOCb readcb,
+             TimerCb timeoutcb, void *data);
+  ~Connection();
+
+  void disconnect();
+
+  int tls_handshake();
+
+  // All write_* and writev_clear functions return number of bytes
+  // written.  If nothing cannot be written (e.g., there is no
+  // allowance in RateLimit or underlying connection blocks), return
+  // 0.  SHRPX_ERR_NETWORK is returned in case of error.
+  //
+  // All read_* functions return number of bytes read.  If nothing
+  // cannot be read (e.g., there is no allowance in Ratelimit or
+  // underlying connection blocks), return 0.  SHRPX_ERR_EOF is
+  // returned in case of EOF and no data was read.  Otherwise
+  // SHRPX_ERR_NETWORK is return in case of error.
+  ssize_t write_tls(const void *data, size_t len);
+  ssize_t read_tls(void *data, size_t len);
+
+  ssize_t get_tls_write_limit();
+  // Updates the number of bytes written in warm up period.
+  void update_tls_warmup_writelen(size_t n);
+
+  ssize_t write_clear(const void *data, size_t len);
+  ssize_t writev_clear(struct iovec *iov, int iovcnt);
+  ssize_t read_clear(void *data, size_t len);
+
+  TLSConnection tls;
+  ev_io wev;
+  ev_io rev;
+  ev_timer wt;
+  ev_timer rt;
+  RateLimit wlimit;
+  RateLimit rlimit;
+  IOCb writecb;
+  IOCb readcb;
+  TimerCb timeoutcb;
+  struct ev_loop *loop;
+  void *data;
+  int fd;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECTION_H
diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc
new file mode 100644 (file)
index 0000000..930ea07
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_connection_handler.h"
+
+#include <unistd.h>
+
+#include <cerrno>
+#include <thread>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_ssl.h"
+#include "shrpx_worker.h"
+#include "shrpx_worker_config.h"
+#include "shrpx_config.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_accept_handler.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
+  auto h = static_cast<ConnectionHandler *>(w->data);
+
+  // If we are in graceful shutdown period, we must not enable
+  // acceptors again.
+  if (worker_config->graceful_shutdown) {
+    return;
+  }
+
+  h->enable_acceptor();
+}
+} // namespace
+
+ConnectionHandler::ConnectionHandler(struct ev_loop *loop)
+    : loop_(loop), sv_ssl_ctx_(nullptr), cl_ssl_ctx_(nullptr),
+      // rate_limit_group_(bufferevent_rate_limit_group_new(
+      //     evbase, get_config()->worker_rate_limit_cfg)),
+      worker_stat_(make_unique<WorkerStat>()), worker_round_robin_cnt_(0) {
+  ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
+  disable_acceptor_timer_.data = this;
+}
+
+ConnectionHandler::~ConnectionHandler() {
+  //  bufferevent_rate_limit_group_free(rate_limit_group_);
+  ev_timer_stop(loop_, &disable_acceptor_timer_);
+}
+
+void ConnectionHandler::create_ssl_context() {
+  sv_ssl_ctx_ = ssl::setup_server_ssl_context();
+  cl_ssl_ctx_ = ssl::setup_client_ssl_context();
+}
+
+void ConnectionHandler::worker_reopen_log_files() {
+  WorkerEvent wev;
+
+  memset(&wev, 0, sizeof(wev));
+  wev.type = REOPEN_LOG;
+
+  for (auto &worker : workers_) {
+    worker->send(wev);
+  }
+}
+
+void ConnectionHandler::worker_renew_ticket_keys(
+    const std::shared_ptr<TicketKeys> &ticket_keys) {
+  WorkerEvent wev;
+
+  memset(&wev, 0, sizeof(wev));
+  wev.type = RENEW_TICKET_KEYS;
+  wev.ticket_keys = ticket_keys;
+
+  for (auto &worker : workers_) {
+    worker->send(wev);
+  }
+}
+
+void ConnectionHandler::create_worker_thread(size_t num) {
+#ifndef NOTHREADS
+  assert(workers_.size() == 0);
+
+  for (size_t i = 0; i < num; ++i) {
+    workers_.push_back(make_unique<Worker>(sv_ssl_ctx_, cl_ssl_ctx_,
+                                           worker_config->cert_tree,
+                                           worker_config->ticket_keys));
+
+    if (LOG_ENABLED(INFO)) {
+      LLOG(INFO, this) << "Created thread #" << workers_.size() - 1;
+    }
+  }
+#endif // NOTHREADS
+}
+
+void ConnectionHandler::join_worker() {
+#ifndef NOTHREADS
+  int n = 0;
+
+  if (LOG_ENABLED(INFO)) {
+    LLOG(INFO, this) << "Waiting for worker thread to join: n="
+                     << workers_.size();
+  }
+
+  for (auto &worker : workers_) {
+    worker->wait();
+    if (LOG_ENABLED(INFO)) {
+      LLOG(INFO, this) << "Thread #" << n << " joined";
+    }
+    ++n;
+  }
+#endif // NOTHREADS
+}
+
+void ConnectionHandler::graceful_shutdown_worker() {
+  if (get_config()->num_worker == 1) {
+    return;
+  }
+
+  WorkerEvent wev;
+  memset(&wev, 0, sizeof(wev));
+  wev.type = GRACEFUL_SHUTDOWN;
+
+  if (LOG_ENABLED(INFO)) {
+    LLOG(INFO, this) << "Sending graceful shutdown signal to worker";
+  }
+
+  for (auto &worker : workers_) {
+
+    worker->send(wev);
+  }
+}
+
+int ConnectionHandler::handle_connection(int fd, sockaddr *addr, int addrlen) {
+  if (LOG_ENABLED(INFO)) {
+    LLOG(INFO, this) << "Accepted connection. fd=" << fd;
+  }
+
+  if (get_config()->num_worker == 1) {
+
+    if (worker_stat_->num_connections >=
+        get_config()->worker_frontend_connections) {
+
+      if (LOG_ENABLED(INFO)) {
+        LLOG(INFO, this) << "Too many connections >="
+                         << get_config()->worker_frontend_connections;
+      }
+
+      close(fd);
+      return -1;
+    }
+
+    auto client = ssl::accept_connection(loop_, sv_ssl_ctx_, fd, addr, addrlen,
+                                         worker_stat_.get(), &dconn_pool_);
+    if (!client) {
+      LLOG(ERROR, this) << "ClientHandler creation failed";
+
+      close(fd);
+      return -1;
+    }
+
+    client->set_http2_session(http2session_.get());
+    client->set_http1_connect_blocker(http1_connect_blocker_.get());
+
+    return 0;
+  }
+
+  size_t idx = worker_round_robin_cnt_ % workers_.size();
+  ++worker_round_robin_cnt_;
+  WorkerEvent wev;
+  memset(&wev, 0, sizeof(wev));
+  wev.type = NEW_CONNECTION;
+  wev.client_fd = fd;
+  memcpy(&wev.client_addr, addr, addrlen);
+  wev.client_addrlen = addrlen;
+
+  workers_[idx]->send(wev);
+
+  return 0;
+}
+
+struct ev_loop *ConnectionHandler::get_loop() const {
+  return loop_;
+}
+
+void ConnectionHandler::create_http2_session() {
+  http2session_ = make_unique<Http2Session>(loop_, cl_ssl_ctx_);
+}
+
+void ConnectionHandler::create_http1_connect_blocker() {
+  http1_connect_blocker_ = make_unique<ConnectBlocker>(loop_);
+}
+
+const WorkerStat *ConnectionHandler::get_worker_stat() const {
+  return worker_stat_.get();
+}
+
+void ConnectionHandler::set_acceptor4(std::unique_ptr<AcceptHandler> h) {
+  acceptor4_ = std::move(h);
+}
+
+AcceptHandler *ConnectionHandler::get_acceptor4() const {
+  return acceptor4_.get();
+}
+
+void ConnectionHandler::set_acceptor6(std::unique_ptr<AcceptHandler> h) {
+  acceptor6_ = std::move(h);
+}
+
+AcceptHandler *ConnectionHandler::get_acceptor6() const {
+  return acceptor6_.get();
+}
+
+void ConnectionHandler::enable_acceptor() {
+  if (acceptor4_) {
+    acceptor4_->enable();
+  }
+
+  if (acceptor6_) {
+    acceptor6_->enable();
+  }
+}
+
+void ConnectionHandler::disable_acceptor() {
+  if (acceptor4_) {
+    acceptor4_->disable();
+  }
+
+  if (acceptor6_) {
+    acceptor6_->disable();
+  }
+}
+
+void ConnectionHandler::disable_acceptor_temporary(ev_tstamp t) {
+  if (t == 0. || ev_is_active(&disable_acceptor_timer_)) {
+    return;
+  }
+
+  disable_acceptor();
+
+  ev_timer_set(&disable_acceptor_timer_, t, 0.);
+  ev_timer_start(loop_, &disable_acceptor_timer_);
+}
+
+void ConnectionHandler::accept_pending_connection() {
+  if (acceptor4_) {
+    acceptor4_->accept_connection();
+  }
+  if (acceptor6_) {
+    acceptor6_->accept_connection();
+  }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_connection_handler.h b/src/shrpx_connection_handler.h
new file mode 100644 (file)
index 0000000..bac31d9
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_CONNECTION_HANDLER_H
+#define SHRPX_CONNECTION_HANDLER_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <memory>
+#include <vector>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include "shrpx_downstream_connection_pool.h"
+
+namespace shrpx {
+
+class Http2Session;
+class ConnectBlocker;
+class AcceptHandler;
+class Worker;
+struct WorkerStat;
+struct TicketKeys;
+
+// TODO should be renamed as ConnectionHandler
+class ConnectionHandler {
+public:
+  ConnectionHandler(struct ev_loop *loop);
+  ~ConnectionHandler();
+  int handle_connection(int fd, sockaddr *addr, int addrlen);
+  void create_ssl_context();
+  void create_worker_thread(size_t num);
+  void worker_reopen_log_files();
+  void worker_renew_ticket_keys(const std::shared_ptr<TicketKeys> &ticket_keys);
+  struct ev_loop *get_loop() const;
+  void create_http2_session();
+  void create_http1_connect_blocker();
+  const WorkerStat *get_worker_stat() const;
+  void set_acceptor4(std::unique_ptr<AcceptHandler> h);
+  AcceptHandler *get_acceptor4() const;
+  void set_acceptor6(std::unique_ptr<AcceptHandler> h);
+  AcceptHandler *get_acceptor6() const;
+  void enable_acceptor();
+  void disable_acceptor();
+  void disable_acceptor_temporary(ev_tstamp t);
+  void accept_pending_connection();
+  void graceful_shutdown_worker();
+  void join_worker();
+
+private:
+  DownstreamConnectionPool dconn_pool_;
+  std::vector<std::unique_ptr<Worker>> workers_;
+  struct ev_loop *loop_;
+  // The frontend server SSL_CTX
+  SSL_CTX *sv_ssl_ctx_;
+  // The backend server SSL_CTX
+  SSL_CTX *cl_ssl_ctx_;
+  // Shared backend HTTP2 session. NULL if multi-threaded. In
+  // multi-threaded case, see shrpx_worker.cc.
+  std::unique_ptr<Http2Session> http2session_;
+  std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
+  // bufferevent_rate_limit_group *rate_limit_group_;
+  std::unique_ptr<AcceptHandler> acceptor4_;
+  std::unique_ptr<AcceptHandler> acceptor6_;
+  ev_timer disable_acceptor_timer_;
+  std::unique_ptr<WorkerStat> worker_stat_;
+  unsigned int worker_round_robin_cnt_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_CONNECTION_HANDLER_H
diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc
new file mode 100644 (file)
index 0000000..7622e1b
--- /dev/null
@@ -0,0 +1,1067 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream.h"
+
+#include <cassert>
+
+#include "http-parser/http_parser.h"
+
+#include "shrpx_upstream.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_downstream_connection.h"
+#include "util.h"
+#include "http2.h"
+
+namespace shrpx {
+
+namespace {
+void upstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto downstream = static_cast<Downstream *>(w->data);
+  auto upstream = downstream->get_upstream();
+
+  auto which = revents == EV_READ ? "read" : "write";
+
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, downstream) << "upstream timeout stream_id="
+                           << downstream->get_stream_id() << " event=" << which;
+  }
+
+  downstream->disable_upstream_rtimer();
+  downstream->disable_upstream_wtimer();
+
+  upstream->on_timeout(downstream);
+}
+} // namespace
+
+namespace {
+void upstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  upstream_timeoutcb(loop, w, EV_READ);
+}
+} // namespace
+
+namespace {
+void upstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  upstream_timeoutcb(loop, w, EV_WRITE);
+}
+} // namespace
+
+namespace {
+void downstream_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto downstream = static_cast<Downstream *>(w->data);
+
+  auto which = revents == EV_READ ? "read" : "write";
+
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, downstream) << "downstream timeout stream_id="
+                           << downstream->get_downstream_stream_id()
+                           << " event=" << which;
+  }
+
+  downstream->disable_downstream_rtimer();
+  downstream->disable_downstream_wtimer();
+
+  auto dconn = downstream->get_downstream_connection();
+
+  if (dconn) {
+    dconn->on_timeout();
+  }
+}
+} // namespace
+
+namespace {
+void downstream_rtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  downstream_timeoutcb(loop, w, EV_READ);
+}
+} // namespace
+
+namespace {
+void downstream_wtimeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  downstream_timeoutcb(loop, w, EV_WRITE);
+}
+} // namespace
+
+// upstream could be nullptr for unittests
+Downstream::Downstream(Upstream *upstream, int32_t stream_id, int32_t priority)
+    : request_start_time_(std::chrono::high_resolution_clock::now()),
+      request_buf_(upstream ? upstream->get_mcpool() : nullptr),
+      response_buf_(upstream ? upstream->get_mcpool() : nullptr),
+      request_bodylen_(0), response_bodylen_(0), response_sent_bodylen_(0),
+      request_content_length_(-1), response_content_length_(-1),
+      upstream_(upstream), request_headers_sum_(0), response_headers_sum_(0),
+      request_datalen_(0), response_datalen_(0), num_retry_(0),
+      stream_id_(stream_id), priority_(priority), downstream_stream_id_(-1),
+      response_rst_stream_error_code_(NGHTTP2_NO_ERROR),
+      request_state_(INITIAL), request_major_(1), request_minor_(1),
+      response_state_(INITIAL), response_http_status_(0), response_major_(1),
+      response_minor_(1), upgrade_request_(false), upgraded_(false),
+      http2_upgrade_seen_(false), chunked_request_(false),
+      request_connection_close_(false), request_header_key_prev_(false),
+      request_http2_expect_body_(false), chunked_response_(false),
+      response_connection_close_(false), response_header_key_prev_(false),
+      expect_final_response_(false) {
+
+  ev_timer_init(&upstream_rtimer_, &upstream_rtimeoutcb, 0.,
+                get_config()->stream_read_timeout);
+  ev_timer_init(&upstream_wtimer_, &upstream_wtimeoutcb, 0.,
+                get_config()->stream_write_timeout);
+  ev_timer_init(&downstream_rtimer_, &downstream_rtimeoutcb, 0.,
+                get_config()->stream_read_timeout);
+  ev_timer_init(&downstream_wtimer_, &downstream_wtimeoutcb, 0.,
+                get_config()->stream_write_timeout);
+
+  upstream_rtimer_.data = this;
+  upstream_wtimer_.data = this;
+  downstream_rtimer_.data = this;
+  downstream_wtimer_.data = this;
+
+  http2::init_hdidx(request_hdidx_);
+  http2::init_hdidx(response_hdidx_);
+}
+
+Downstream::~Downstream() {
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, this) << "Deleting";
+  }
+
+  // check nullptr for unittest
+  if (upstream_) {
+    auto loop = upstream_->get_client_handler()->get_loop();
+
+    ev_timer_stop(loop, &upstream_rtimer_);
+    ev_timer_stop(loop, &upstream_wtimer_);
+    ev_timer_stop(loop, &downstream_rtimer_);
+    ev_timer_stop(loop, &downstream_wtimer_);
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, this) << "Deleted";
+  }
+}
+
+int Downstream::attach_downstream_connection(
+    std::unique_ptr<DownstreamConnection> dconn) {
+  if (dconn->attach_downstream(this) != 0) {
+    return -1;
+  }
+
+  dconn_ = std::move(dconn);
+
+  return 0;
+}
+
+void Downstream::detach_downstream_connection() {
+  if (!dconn_) {
+    return;
+  }
+
+  dconn_->detach_downstream(this);
+
+  auto handler = dconn_->get_client_handler();
+
+  handler->pool_downstream_connection(
+      std::unique_ptr<DownstreamConnection>(dconn_.release()));
+}
+
+void Downstream::release_downstream_connection() { dconn_.release(); }
+
+DownstreamConnection *Downstream::get_downstream_connection() {
+  return dconn_.get();
+}
+
+std::unique_ptr<DownstreamConnection> Downstream::pop_downstream_connection() {
+  return std::unique_ptr<DownstreamConnection>(dconn_.release());
+}
+
+void Downstream::pause_read(IOCtrlReason reason) {
+  if (dconn_) {
+    dconn_->pause_read(reason);
+  }
+}
+
+int Downstream::resume_read(IOCtrlReason reason, size_t consumed) {
+  if (dconn_) {
+    return dconn_->resume_read(reason, consumed);
+  }
+
+  return 0;
+}
+
+void Downstream::force_resume_read() {
+  if (dconn_) {
+    dconn_->force_resume_read();
+  }
+}
+
+namespace {
+const Headers::value_type *get_header_linear(const Headers &headers,
+                                             const std::string &name) {
+  const Headers::value_type *res = nullptr;
+  for (auto &kv : headers) {
+    if (kv.name == name) {
+      res = &kv;
+    }
+  }
+  return res;
+}
+} // namespace
+
+const Headers &Downstream::get_request_headers() const {
+  return request_headers_;
+}
+
+void Downstream::assemble_request_cookie() {
+  std::string &cookie = assembled_request_cookie_;
+  cookie = "";
+  for (auto &kv : request_headers_) {
+    if (kv.name.size() != 6 || kv.name[5] != 'e' ||
+        !util::streq("cooki", kv.name.c_str(), 5)) {
+      continue;
+    }
+
+    auto end = kv.value.find_last_not_of(" ;");
+    if (end == std::string::npos) {
+      cookie += kv.value;
+    } else {
+      cookie.append(std::begin(kv.value), std::begin(kv.value) + end + 1);
+    }
+    cookie += "; ";
+  }
+  if (cookie.size() >= 2) {
+    cookie.erase(cookie.size() - 2);
+  }
+}
+
+Headers Downstream::crumble_request_cookie() {
+  Headers cookie_hdrs;
+  for (auto &kv : request_headers_) {
+    if (kv.name.size() != 6 || kv.name[5] != 'e' ||
+        !util::streq("cooki", kv.name.c_str(), 5)) {
+      continue;
+    }
+    size_t last = kv.value.size();
+
+    for (size_t j = 0; j < last;) {
+      j = kv.value.find_first_not_of("\t ;", j);
+      if (j == std::string::npos) {
+        break;
+      }
+      auto first = j;
+
+      j = kv.value.find(';', j);
+      if (j == std::string::npos) {
+        j = last;
+      }
+
+      cookie_hdrs.push_back(
+          Header("cookie", kv.value.substr(first, j - first), kv.no_index));
+    }
+  }
+  return cookie_hdrs;
+}
+
+const std::string &Downstream::get_assembled_request_cookie() const {
+  return assembled_request_cookie_;
+}
+
+namespace {
+int index_headers(http2::HeaderIndex &hdidx, Headers &headers,
+                  int64_t &content_length) {
+  for (size_t i = 0; i < headers.size(); ++i) {
+    auto &kv = headers[i];
+    util::inp_strlower(kv.name);
+
+    auto token = http2::lookup_token(
+        reinterpret_cast<const uint8_t *>(kv.name.c_str()), kv.name.size());
+    if (token < 0) {
+      continue;
+    }
+
+    kv.token = token;
+    http2::index_header(hdidx, token, i);
+
+    if (token == http2::HD_CONTENT_LENGTH) {
+      auto len = util::parse_uint(kv.value);
+      if (len == -1) {
+        return -1;
+      }
+      if (content_length != -1) {
+        return -1;
+      }
+      content_length = len;
+    }
+  }
+  return 0;
+}
+} // namespace
+
+int Downstream::index_request_headers() {
+  return index_headers(request_hdidx_, request_headers_,
+                       request_content_length_);
+}
+
+const Headers::value_type *Downstream::get_request_header(int16_t token) const {
+  return http2::get_header(request_hdidx_, token, request_headers_);
+}
+
+const Headers::value_type *
+Downstream::get_request_header(const std::string &name) const {
+  return get_header_linear(request_headers_, name);
+}
+
+void Downstream::add_request_header(std::string name, std::string value) {
+  request_header_key_prev_ = true;
+  request_headers_sum_ += name.size() + value.size();
+  request_headers_.emplace_back(std::move(name), std::move(value));
+}
+
+void Downstream::set_last_request_header_value(std::string value) {
+  request_header_key_prev_ = false;
+  request_headers_sum_ += value.size();
+  Headers::value_type &item = request_headers_.back();
+  item.value = std::move(value);
+}
+
+void Downstream::add_request_header(const uint8_t *name, size_t namelen,
+                                    const uint8_t *value, size_t valuelen,
+                                    bool no_index, int16_t token) {
+  http2::index_header(request_hdidx_, token, request_headers_.size());
+  request_headers_sum_ += namelen + valuelen;
+  http2::add_header(request_headers_, name, namelen, value, valuelen, no_index,
+                    token);
+}
+
+bool Downstream::get_request_header_key_prev() const {
+  return request_header_key_prev_;
+}
+
+void Downstream::append_last_request_header_key(const char *data, size_t len) {
+  assert(request_header_key_prev_);
+  request_headers_sum_ += len;
+  auto &item = request_headers_.back();
+  item.name.append(data, len);
+}
+
+void Downstream::append_last_request_header_value(const char *data,
+                                                  size_t len) {
+  assert(!request_header_key_prev_);
+  request_headers_sum_ += len;
+  auto &item = request_headers_.back();
+  item.value.append(data, len);
+}
+
+void Downstream::clear_request_headers() {
+  Headers().swap(request_headers_);
+  http2::init_hdidx(request_hdidx_);
+}
+
+size_t Downstream::get_request_headers_sum() const {
+  return request_headers_sum_;
+}
+
+void Downstream::set_request_method(std::string method) {
+  request_method_ = std::move(method);
+}
+
+const std::string &Downstream::get_request_method() const {
+  return request_method_;
+}
+
+void Downstream::set_request_path(std::string path) {
+  request_path_ = std::move(path);
+}
+
+void Downstream::append_request_path(const char *data, size_t len) {
+  request_path_.append(data, len);
+}
+
+const std::string &Downstream::get_request_path() const {
+  return request_path_;
+}
+
+void Downstream::set_request_start_time(
+    std::chrono::high_resolution_clock::time_point time) {
+  request_start_time_ = std::move(time);
+}
+
+const std::chrono::high_resolution_clock::time_point &
+Downstream::get_request_start_time() const {
+  return request_start_time_;
+}
+
+const std::string &Downstream::get_request_http2_scheme() const {
+  return request_http2_scheme_;
+}
+
+void Downstream::set_request_http2_scheme(std::string scheme) {
+  request_http2_scheme_ = std::move(scheme);
+}
+
+const std::string &Downstream::get_request_http2_authority() const {
+  return request_http2_authority_;
+}
+
+void Downstream::set_request_http2_authority(std::string authority) {
+  request_http2_authority_ = std::move(authority);
+}
+
+void Downstream::set_request_major(int major) { request_major_ = major; }
+
+void Downstream::set_request_minor(int minor) { request_minor_ = minor; }
+
+int Downstream::get_request_major() const { return request_major_; }
+
+int Downstream::get_request_minor() const { return request_minor_; }
+
+void Downstream::reset_upstream(Upstream *upstream) {
+  upstream_ = upstream;
+  if (dconn_) {
+    dconn_->on_upstream_change(upstream);
+  }
+}
+
+Upstream *Downstream::get_upstream() const { return upstream_; }
+
+void Downstream::set_stream_id(int32_t stream_id) { stream_id_ = stream_id; }
+
+int32_t Downstream::get_stream_id() const { return stream_id_; }
+
+void Downstream::set_request_state(int state) { request_state_ = state; }
+
+int Downstream::get_request_state() const { return request_state_; }
+
+bool Downstream::get_chunked_request() const { return chunked_request_; }
+
+void Downstream::set_chunked_request(bool f) { chunked_request_ = f; }
+
+bool Downstream::get_request_connection_close() const {
+  return request_connection_close_;
+}
+
+void Downstream::set_request_connection_close(bool f) {
+  request_connection_close_ = f;
+}
+
+bool Downstream::get_request_http2_expect_body() const {
+  return request_http2_expect_body_;
+}
+
+void Downstream::set_request_http2_expect_body(bool f) {
+  request_http2_expect_body_ = f;
+}
+
+bool Downstream::request_buf_full() {
+  if (dconn_) {
+    return request_buf_.rleft() >= get_config()->downstream_request_buffer_size;
+  } else {
+    return false;
+  }
+}
+
+DefaultMemchunks *Downstream::get_request_buf() { return &request_buf_; }
+
+// Call this function after this object is attached to
+// Downstream. Otherwise, the program will crash.
+int Downstream::push_request_headers() {
+  if (!dconn_) {
+    DLOG(INFO, this) << "dconn_ is NULL";
+    return -1;
+  }
+  return dconn_->push_request_headers();
+}
+
+int Downstream::push_upload_data_chunk(const uint8_t *data, size_t datalen) {
+  // Assumes that request headers have already been pushed to output
+  // buffer using push_request_headers().
+  if (!dconn_) {
+    DLOG(INFO, this) << "dconn_ is NULL";
+    return -1;
+  }
+  request_bodylen_ += datalen;
+  if (dconn_->push_upload_data_chunk(data, datalen) != 0) {
+    return -1;
+  }
+
+  request_datalen_ += datalen;
+
+  return 0;
+}
+
+int Downstream::end_upload_data() {
+  if (!dconn_) {
+    DLOG(INFO, this) << "dconn_ is NULL";
+    return -1;
+  }
+  return dconn_->end_upload_data();
+}
+
+const Headers &Downstream::get_response_headers() const {
+  return response_headers_;
+}
+
+int Downstream::index_response_headers() {
+  return index_headers(response_hdidx_, response_headers_,
+                       response_content_length_);
+}
+
+const Headers::value_type *
+Downstream::get_response_header(int16_t token) const {
+  return http2::get_header(response_hdidx_, token, response_headers_);
+}
+
+void Downstream::rewrite_location_response_header(
+    const std::string &upstream_scheme) {
+  auto hd =
+      http2::get_header(response_hdidx_, http2::HD_LOCATION, response_headers_);
+  if (!hd) {
+    return;
+  }
+  http_parser_url u;
+  memset(&u, 0, sizeof(u));
+  int rv =
+      http_parser_parse_url((*hd).value.c_str(), (*hd).value.size(), 0, &u);
+  if (rv != 0) {
+    return;
+  }
+  std::string new_uri;
+  if (get_config()->no_host_rewrite) {
+    if (!request_http2_authority_.empty()) {
+      new_uri = http2::rewrite_location_uri(
+          (*hd).value, u, request_http2_authority_, request_http2_authority_,
+          upstream_scheme);
+    }
+    if (new_uri.empty()) {
+      auto host = get_request_header(http2::HD_HOST);
+      if (host) {
+        new_uri = http2::rewrite_location_uri((*hd).value, u, (*host).value,
+                                              (*host).value, upstream_scheme);
+      } else if (!request_downstream_host_.empty()) {
+        new_uri = http2::rewrite_location_uri(
+            (*hd).value, u, request_downstream_host_, "", upstream_scheme);
+      } else {
+        return;
+      }
+    }
+  } else {
+    if (request_downstream_host_.empty()) {
+      return;
+    }
+    if (!request_http2_authority_.empty()) {
+      new_uri = http2::rewrite_location_uri(
+          (*hd).value, u, request_downstream_host_, request_http2_authority_,
+          upstream_scheme);
+    } else {
+      auto host = get_request_header(http2::HD_HOST);
+      if (host) {
+        new_uri = http2::rewrite_location_uri((*hd).value, u,
+                                              request_downstream_host_,
+                                              (*host).value, upstream_scheme);
+      } else {
+        new_uri = http2::rewrite_location_uri(
+            (*hd).value, u, request_downstream_host_, "", upstream_scheme);
+      }
+    }
+  }
+  if (!new_uri.empty()) {
+    auto idx = response_hdidx_[http2::HD_LOCATION];
+    response_headers_[idx].value = std::move(new_uri);
+  }
+}
+
+void Downstream::add_response_header(std::string name, std::string value) {
+  response_header_key_prev_ = true;
+  response_headers_sum_ += name.size() + value.size();
+  response_headers_.emplace_back(std::move(name), std::move(value));
+}
+
+void Downstream::set_last_response_header_value(std::string value) {
+  response_header_key_prev_ = false;
+  response_headers_sum_ += value.size();
+  auto &item = response_headers_.back();
+  item.value = std::move(value);
+}
+
+void Downstream::add_response_header(std::string name, std::string value,
+                                     int16_t token) {
+  http2::index_header(response_hdidx_, token, response_headers_.size());
+  response_headers_sum_ += name.size() + value.size();
+  response_headers_.emplace_back(std::move(name), std::move(value), false,
+                                 token);
+}
+
+void Downstream::add_response_header(const uint8_t *name, size_t namelen,
+                                     const uint8_t *value, size_t valuelen,
+                                     bool no_index, int16_t token) {
+  http2::index_header(response_hdidx_, token, response_headers_.size());
+  response_headers_sum_ += namelen + valuelen;
+  http2::add_header(response_headers_, name, namelen, value, valuelen, no_index,
+                    token);
+}
+
+bool Downstream::get_response_header_key_prev() const {
+  return response_header_key_prev_;
+}
+
+void Downstream::append_last_response_header_key(const char *data, size_t len) {
+  assert(response_header_key_prev_);
+  response_headers_sum_ += len;
+  auto &item = response_headers_.back();
+  item.name.append(data, len);
+}
+
+void Downstream::append_last_response_header_value(const char *data,
+                                                   size_t len) {
+  assert(!response_header_key_prev_);
+  response_headers_sum_ += len;
+  auto &item = response_headers_.back();
+  item.value.append(data, len);
+}
+
+void Downstream::clear_response_headers() {
+  Headers().swap(response_headers_);
+  http2::init_hdidx(response_hdidx_);
+}
+
+size_t Downstream::get_response_headers_sum() const {
+  return response_headers_sum_;
+}
+
+unsigned int Downstream::get_response_http_status() const {
+  return response_http_status_;
+}
+
+void Downstream::set_response_http_status(unsigned int status) {
+  response_http_status_ = status;
+}
+
+void Downstream::set_response_major(int major) { response_major_ = major; }
+
+void Downstream::set_response_minor(int minor) { response_minor_ = minor; }
+
+int Downstream::get_response_major() const { return response_major_; }
+
+int Downstream::get_response_minor() const { return response_minor_; }
+
+int Downstream::get_response_version() const {
+  return response_major_ * 100 + response_minor_;
+}
+
+bool Downstream::get_chunked_response() const { return chunked_response_; }
+
+void Downstream::set_chunked_response(bool f) { chunked_response_ = f; }
+
+bool Downstream::get_response_connection_close() const {
+  return response_connection_close_;
+}
+
+void Downstream::set_response_connection_close(bool f) {
+  response_connection_close_ = f;
+}
+
+int Downstream::on_read() {
+  if (!dconn_) {
+    DLOG(INFO, this) << "dconn_ is NULL";
+    return -1;
+  }
+  return dconn_->on_read();
+}
+
+int Downstream::change_priority(int32_t pri) {
+  if (!dconn_) {
+    DLOG(INFO, this) << "dconn_ is NULL";
+    return -1;
+  }
+  return dconn_->on_priority_change(pri);
+}
+
+void Downstream::set_response_state(int state) { response_state_ = state; }
+
+int Downstream::get_response_state() const { return response_state_; }
+
+DefaultMemchunks *Downstream::get_response_buf() { return &response_buf_; }
+
+bool Downstream::response_buf_full() {
+  if (dconn_) {
+    return response_buf_.rleft() >=
+           get_config()->downstream_response_buffer_size;
+  } else {
+    return false;
+  }
+}
+
+void Downstream::add_response_bodylen(size_t amount) {
+  response_bodylen_ += amount;
+}
+
+int64_t Downstream::get_response_bodylen() const { return response_bodylen_; }
+
+void Downstream::add_response_sent_bodylen(size_t amount) {
+  response_sent_bodylen_ += amount;
+}
+
+int64_t Downstream::get_response_sent_bodylen() const {
+  return response_sent_bodylen_;
+}
+
+int64_t Downstream::get_response_content_length() const {
+  return response_content_length_;
+}
+
+void Downstream::set_response_content_length(int64_t len) {
+  response_content_length_ = len;
+}
+
+int64_t Downstream::get_request_content_length() const {
+  return request_content_length_;
+}
+
+void Downstream::set_request_content_length(int64_t len) {
+  request_content_length_ = len;
+}
+
+bool Downstream::validate_request_bodylen() const {
+  if (request_content_length_ == -1) {
+    return true;
+  }
+
+  if (request_content_length_ != request_bodylen_) {
+    if (LOG_ENABLED(INFO)) {
+      DLOG(INFO, this) << "request invalid bodylen: content-length="
+                       << request_content_length_
+                       << ", received=" << request_bodylen_;
+    }
+    return false;
+  }
+
+  return true;
+}
+
+bool Downstream::validate_response_bodylen() const {
+  if (!expect_response_body() || response_content_length_ == -1) {
+    return true;
+  }
+
+  if (response_content_length_ != response_bodylen_) {
+    if (LOG_ENABLED(INFO)) {
+      DLOG(INFO, this) << "response invalid bodylen: content-length="
+                       << response_content_length_
+                       << ", received=" << response_bodylen_;
+    }
+    return false;
+  }
+
+  return true;
+}
+
+void Downstream::set_priority(int32_t pri) { priority_ = pri; }
+
+int32_t Downstream::get_priority() const { return priority_; }
+
+void Downstream::check_upgrade_fulfilled() {
+  if (request_method_ == "CONNECT") {
+    upgraded_ = 200 <= response_http_status_ && response_http_status_ < 300;
+
+    return;
+  }
+
+  if (response_http_status_ == 101) {
+    // TODO Do more strict checking for upgrade headers
+    upgraded_ = upgrade_request_;
+
+    return;
+  }
+}
+
+void Downstream::inspect_http2_request() {
+  if (request_method_ == "CONNECT") {
+    upgrade_request_ = true;
+  }
+}
+
+void Downstream::inspect_http1_request() {
+  if (request_method_ == "CONNECT") {
+    upgrade_request_ = true;
+  }
+
+  if (!upgrade_request_) {
+    auto idx = request_hdidx_[http2::HD_UPGRADE];
+    if (idx != -1) {
+      upgrade_request_ = true;
+
+      auto &val = request_headers_[idx].value;
+      // TODO Perform more strict checking for upgrade headers
+      if (util::streq(NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, val.c_str(),
+                      val.size())) {
+        http2_upgrade_seen_ = true;
+      }
+    }
+  }
+  auto idx = request_hdidx_[http2::HD_TRANSFER_ENCODING];
+  if (idx != -1) {
+    request_content_length_ = -1;
+    if (util::strifind(request_headers_[idx].value.c_str(), "chunked")) {
+      chunked_request_ = true;
+    }
+  }
+}
+
+void Downstream::inspect_http1_response() {
+  auto idx = response_hdidx_[http2::HD_TRANSFER_ENCODING];
+  if (idx != -1) {
+    response_content_length_ = -1;
+    if (util::strifind(response_headers_[idx].value.c_str(), "chunked")) {
+      chunked_response_ = true;
+    }
+  }
+}
+
+void Downstream::reset_response() {
+  response_http_status_ = 0;
+  response_major_ = 1;
+  response_minor_ = 1;
+}
+
+bool Downstream::get_non_final_response() const {
+  return response_http_status_ / 100 == 1;
+}
+
+bool Downstream::get_upgraded() const { return upgraded_; }
+
+bool Downstream::get_upgrade_request() const { return upgrade_request_; }
+
+bool Downstream::get_http2_upgrade_request() const {
+  return request_bodylen_ == 0 && http2_upgrade_seen_ &&
+         request_hdidx_[http2::HD_HTTP2_SETTINGS] != -1;
+}
+
+namespace {
+const std::string EMPTY;
+} // namespace
+
+const std::string &Downstream::get_http2_settings() const {
+  auto idx = request_hdidx_[http2::HD_HTTP2_SETTINGS];
+  if (idx == -1) {
+    return EMPTY;
+  }
+  return request_headers_[idx].value;
+}
+
+void Downstream::set_downstream_stream_id(int32_t stream_id) {
+  downstream_stream_id_ = stream_id;
+}
+
+int32_t Downstream::get_downstream_stream_id() const {
+  return downstream_stream_id_;
+}
+
+uint32_t Downstream::get_response_rst_stream_error_code() const {
+  return response_rst_stream_error_code_;
+}
+
+void Downstream::set_response_rst_stream_error_code(uint32_t error_code) {
+  response_rst_stream_error_code_ = error_code;
+}
+
+void Downstream::set_expect_final_response(bool f) {
+  expect_final_response_ = f;
+}
+
+bool Downstream::get_expect_final_response() const {
+  return expect_final_response_;
+}
+
+size_t Downstream::get_request_datalen() const { return request_datalen_; }
+
+void Downstream::dec_request_datalen(size_t len) {
+  assert(request_datalen_ >= len);
+  request_datalen_ -= len;
+}
+
+void Downstream::reset_request_datalen() { request_datalen_ = 0; }
+
+void Downstream::add_response_datalen(size_t len) { response_datalen_ += len; }
+
+void Downstream::dec_response_datalen(size_t len) {
+  assert(response_datalen_ >= len);
+  response_datalen_ -= len;
+}
+
+size_t Downstream::get_response_datalen() const { return response_datalen_; }
+
+void Downstream::reset_response_datalen() { response_datalen_ = 0; }
+
+bool Downstream::expect_response_body() const {
+  return request_method_ != "HEAD" && response_http_status_ / 100 != 1 &&
+         response_http_status_ != 304 && response_http_status_ != 204;
+}
+
+namespace {
+bool pseudo_header_allowed(const Headers &headers) {
+  if (headers.empty()) {
+    return true;
+  }
+
+  return headers.back().name.c_str()[0] == ':';
+}
+} // namespace
+
+bool Downstream::request_pseudo_header_allowed(int16_t token) const {
+  if (!pseudo_header_allowed(request_headers_)) {
+    return false;
+  }
+  return http2::check_http2_request_pseudo_header(request_hdidx_, token);
+}
+
+bool Downstream::response_pseudo_header_allowed(int16_t token) const {
+  if (!pseudo_header_allowed(response_headers_)) {
+    return false;
+  }
+  return http2::check_http2_response_pseudo_header(response_hdidx_, token);
+}
+
+namespace {
+void reset_timer(struct ev_loop *loop, ev_timer *w) { ev_timer_again(loop, w); }
+} // namespace
+
+namespace {
+void try_reset_timer(struct ev_loop *loop, ev_timer *w) {
+  if (!ev_is_active(w)) {
+    return;
+  }
+  ev_timer_again(loop, w);
+}
+} // namespace
+
+namespace {
+void ensure_timer(struct ev_loop *loop, ev_timer *w) {
+  if (ev_is_active(w)) {
+    return;
+  }
+  ev_timer_again(loop, w);
+}
+} // namespace
+
+namespace {
+void disable_timer(struct ev_loop *loop, ev_timer *w) {
+  ev_timer_stop(loop, w);
+}
+} // namespace
+
+void Downstream::reset_upstream_rtimer() {
+  if (get_config()->stream_read_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  reset_timer(loop, &upstream_rtimer_);
+}
+
+void Downstream::reset_upstream_wtimer() {
+  auto loop = upstream_->get_client_handler()->get_loop();
+  if (get_config()->stream_write_timeout != 0.) {
+    reset_timer(loop, &upstream_wtimer_);
+  }
+  if (get_config()->stream_read_timeout != 0.) {
+    try_reset_timer(loop, &upstream_rtimer_);
+  }
+}
+
+void Downstream::ensure_upstream_wtimer() {
+  if (get_config()->stream_write_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  ensure_timer(loop, &upstream_wtimer_);
+}
+
+void Downstream::disable_upstream_rtimer() {
+  if (get_config()->stream_read_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  disable_timer(loop, &upstream_rtimer_);
+}
+
+void Downstream::disable_upstream_wtimer() {
+  if (get_config()->stream_write_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  disable_timer(loop, &upstream_wtimer_);
+}
+
+void Downstream::reset_downstream_rtimer() {
+  if (get_config()->stream_read_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  reset_timer(loop, &downstream_rtimer_);
+}
+
+void Downstream::reset_downstream_wtimer() {
+  auto loop = upstream_->get_client_handler()->get_loop();
+  if (get_config()->stream_write_timeout != 0.) {
+    reset_timer(loop, &downstream_wtimer_);
+  }
+  if (get_config()->stream_read_timeout != 0.) {
+    try_reset_timer(loop, &downstream_rtimer_);
+  }
+}
+
+void Downstream::ensure_downstream_wtimer() {
+  if (get_config()->stream_write_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  ensure_timer(loop, &downstream_wtimer_);
+}
+
+void Downstream::disable_downstream_rtimer() {
+  if (get_config()->stream_read_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  disable_timer(loop, &downstream_rtimer_);
+}
+
+void Downstream::disable_downstream_wtimer() {
+  if (get_config()->stream_write_timeout == 0.) {
+    return;
+  }
+  auto loop = upstream_->get_client_handler()->get_loop();
+  disable_timer(loop, &downstream_wtimer_);
+}
+
+bool Downstream::accesslog_ready() const { return response_http_status_ > 0; }
+
+void Downstream::add_retry() { ++num_retry_; }
+
+bool Downstream::no_more_retry() const { return num_retry_ > 5; }
+
+void Downstream::set_request_downstream_host(std::string host) {
+  request_downstream_host_ = std::move(host);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h
new file mode 100644 (file)
index 0000000..810155e
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_H
+#define SHRPX_DOWNSTREAM_H
+
+#include "shrpx.h"
+
+#include <stdint.h>
+
+#include <vector>
+#include <string>
+#include <memory>
+#include <chrono>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_io_control.h"
+#include "http2.h"
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Upstream;
+class DownstreamConnection;
+
+class Downstream {
+public:
+  Downstream(Upstream *upstream, int32_t stream_id, int32_t priority);
+  ~Downstream();
+  void reset_upstream(Upstream *upstream);
+  Upstream *get_upstream() const;
+  void set_stream_id(int32_t stream_id);
+  int32_t get_stream_id() const;
+  void set_priority(int32_t pri);
+  int32_t get_priority() const;
+  void pause_read(IOCtrlReason reason);
+  int resume_read(IOCtrlReason reason, size_t consumed);
+  void force_resume_read();
+  // Set stream ID for downstream HTTP2 connection.
+  void set_downstream_stream_id(int32_t stream_id);
+  int32_t get_downstream_stream_id() const;
+
+  int attach_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+  void detach_downstream_connection();
+  // Releases dconn_, without freeing it.
+  void release_downstream_connection();
+  DownstreamConnection *get_downstream_connection();
+  // Returns dconn_ and nullifies dconn_.
+  std::unique_ptr<DownstreamConnection> pop_downstream_connection();
+
+  // Returns true if output buffer is full. If underlying dconn_ is
+  // NULL, this function always returns false.
+  bool request_buf_full();
+  // Returns true if upgrade (HTTP Upgrade or CONNECT) is succeeded.
+  void check_upgrade_fulfilled();
+  // Returns true if the request is upgrade.
+  bool get_upgrade_request() const;
+  // Returns true if the upgrade is succeded as a result of the call
+  // check_upgrade_fulfilled().
+  bool get_upgraded() const;
+  // Inspects HTTP/2 request.
+  void inspect_http2_request();
+  // Inspects HTTP/1 request.  This checks whether the request is
+  // upgrade request and tranfer-encoding etc.
+  void inspect_http1_request();
+  // Returns true if the request is HTTP Upgrade for HTTP/2
+  bool get_http2_upgrade_request() const;
+  // Returns the value of HTTP2-Settings request header field.
+  const std::string &get_http2_settings() const;
+  // downstream request API
+  const Headers &get_request_headers() const;
+  // Crumbles (split cookie by ";") in request_headers_ and returns
+  // them.  Headers::no_index is inherited.
+  Headers crumble_request_cookie();
+  void assemble_request_cookie();
+  const std::string &get_assembled_request_cookie() const;
+  // Lower the request header field names and indexes request headers.
+  // If there is any invalid headers (e.g., multiple Content-Length
+  // having different values), returns -1.
+  int index_request_headers();
+  // Returns pointer to the request header with the name |name|.  If
+  // multiple header have |name| as name, return last occurrence from
+  // the beginning.  If no such header is found, returns nullptr.
+  // This function must be called after headers are indexed
+  const Headers::value_type *get_request_header(int16_t token) const;
+  // Returns pointer to the request header with the name |name|.  If
+  // no such header is found, returns nullptr.
+  const Headers::value_type *get_request_header(const std::string &name) const;
+  void add_request_header(std::string name, std::string value);
+  void set_last_request_header_value(std::string value);
+
+  void add_request_header(const uint8_t *name, size_t namelen,
+                          const uint8_t *value, size_t valuelen, bool no_index,
+                          int16_t token);
+
+  bool get_request_header_key_prev() const;
+  void append_last_request_header_key(const char *data, size_t len);
+  void append_last_request_header_value(const char *data, size_t len);
+  // Empties request headers.
+  void clear_request_headers();
+
+  size_t get_request_headers_sum() const;
+
+  void set_request_method(std::string method);
+  const std::string &get_request_method() const;
+  void set_request_path(std::string path);
+  void
+  set_request_start_time(std::chrono::high_resolution_clock::time_point time);
+  const std::chrono::high_resolution_clock::time_point &
+  get_request_start_time() const;
+  void append_request_path(const char *data, size_t len);
+  // Returns request path. For HTTP/1.1, this is request-target. For
+  // HTTP/2, this is :path header field value.
+  const std::string &get_request_path() const;
+  // Returns HTTP/2 :scheme header field value.
+  const std::string &get_request_http2_scheme() const;
+  void set_request_http2_scheme(std::string scheme);
+  // Returns HTTP/2 :authority header field value.
+  const std::string &get_request_http2_authority() const;
+  void set_request_http2_authority(std::string authority);
+  void set_request_major(int major);
+  void set_request_minor(int minor);
+  int get_request_major() const;
+  int get_request_minor() const;
+  int push_request_headers();
+  bool get_chunked_request() const;
+  void set_chunked_request(bool f);
+  bool get_request_connection_close() const;
+  void set_request_connection_close(bool f);
+  bool get_request_http2_expect_body() const;
+  void set_request_http2_expect_body(bool f);
+  int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+  int end_upload_data();
+  size_t get_request_datalen() const;
+  void dec_request_datalen(size_t len);
+  void reset_request_datalen();
+  // Validates that received request body length and content-length
+  // matches.
+  bool validate_request_bodylen() const;
+  int64_t get_request_content_length() const;
+  void set_request_content_length(int64_t len);
+  bool request_pseudo_header_allowed(int16_t token) const;
+  void set_request_downstream_host(std::string host);
+  bool expect_response_body() const;
+  enum {
+    INITIAL,
+    HEADER_COMPLETE,
+    MSG_COMPLETE,
+    STREAM_CLOSED,
+    CONNECT_FAIL,
+    IDLE,
+    MSG_RESET,
+    // header contains invalid header field.  We can safely send error
+    // response (502) to a client.
+    MSG_BAD_HEADER,
+  };
+  void set_request_state(int state);
+  int get_request_state() const;
+  DefaultMemchunks *get_request_buf();
+  // downstream response API
+  const Headers &get_response_headers() const;
+  // Lower the response header field names and indexes response
+  // headers.  If there are invalid headers (e.g., multiple
+  // Content-Length with different values), returns -1.
+  int index_response_headers();
+  // Returns pointer to the response header with the name |name|.  If
+  // multiple header have |name| as name, return last occurrence from
+  // the beginning.  If no such header is found, returns nullptr.
+  // This function must be called after response headers are indexed.
+  const Headers::value_type *get_response_header(int16_t token) const;
+  // Rewrites the location response header field.
+  void rewrite_location_response_header(const std::string &upstream_scheme);
+  void add_response_header(std::string name, std::string value);
+  void set_last_response_header_value(std::string value);
+
+  void add_response_header(std::string name, std::string value, int16_t token);
+  void add_response_header(const uint8_t *name, size_t namelen,
+                           const uint8_t *value, size_t valuelen, bool no_index,
+                           int16_t token);
+
+  bool get_response_header_key_prev() const;
+  void append_last_response_header_key(const char *data, size_t len);
+  void append_last_response_header_value(const char *data, size_t len);
+  // Empties response headers.
+  void clear_response_headers();
+
+  size_t get_response_headers_sum() const;
+
+  unsigned int get_response_http_status() const;
+  void set_response_http_status(unsigned int status);
+  void set_response_major(int major);
+  void set_response_minor(int minor);
+  int get_response_major() const;
+  int get_response_minor() const;
+  int get_response_version() const;
+  bool get_chunked_response() const;
+  void set_chunked_response(bool f);
+  bool get_response_connection_close() const;
+  void set_response_connection_close(bool f);
+  void set_response_state(int state);
+  int get_response_state() const;
+  DefaultMemchunks *get_response_buf();
+  bool response_buf_full();
+  void add_response_bodylen(size_t amount);
+  int64_t get_response_bodylen() const;
+  void add_response_sent_bodylen(size_t amount);
+  int64_t get_response_sent_bodylen() const;
+  int64_t get_response_content_length() const;
+  void set_response_content_length(int64_t len);
+  // Validates that received response body length and content-length
+  // matches.
+  bool validate_response_bodylen() const;
+  uint32_t get_response_rst_stream_error_code() const;
+  void set_response_rst_stream_error_code(uint32_t error_code);
+  // Inspects HTTP/1 response.  This checks tranfer-encoding etc.
+  void inspect_http1_response();
+  // Clears some of member variables for response.
+  void reset_response();
+  bool get_non_final_response() const;
+  void set_expect_final_response(bool f);
+  bool get_expect_final_response() const;
+  void add_response_datalen(size_t len);
+  void dec_response_datalen(size_t len);
+  size_t get_response_datalen() const;
+  void reset_response_datalen();
+  bool response_pseudo_header_allowed(int16_t token) const;
+
+  // Call this method when there is incoming data in downstream
+  // connection.
+  int on_read();
+
+  // Change the priority of downstream
+  int change_priority(int32_t pri);
+
+  // Maximum buffer size for header name/value pairs.
+  static const size_t MAX_HEADERS_SUM = 32768;
+
+  bool get_rst_stream_after_end_stream() const;
+  void set_rst_stream_after_end_stream(bool f);
+
+  // Resets upstream read timer.  If it is active, timeout value is
+  // reset.  If it is not active, timer will be started.
+  void reset_upstream_rtimer();
+  // Resets upstream write timer. If it is active, timeout value is
+  // reset.  If it is not active, timer will be started.  This
+  // function also resets read timer if it has been started.
+  void reset_upstream_wtimer();
+  // Makes sure that upstream write timer is started.  If it has been
+  // started, do nothing.  Otherwise, write timer will be started.
+  void ensure_upstream_wtimer();
+  // Disables upstream read timer.
+  void disable_upstream_rtimer();
+  // Disables upstream write timer.
+  void disable_upstream_wtimer();
+
+  // Downstream timer functions.  They works in a similar way just
+  // like the upstream timer function.
+  void reset_downstream_rtimer();
+  void reset_downstream_wtimer();
+  void ensure_downstream_wtimer();
+  void disable_downstream_rtimer();
+  void disable_downstream_wtimer();
+
+  // Returns true if accesslog can be written for this downstream.
+  bool accesslog_ready() const;
+
+  // Increment retry count
+  void add_retry();
+  // true if retry attempt should not be done.
+  bool no_more_retry() const;
+
+  enum {
+    EVENT_ERROR = 0x1,
+    EVENT_TIMEOUT = 0x2,
+  };
+
+private:
+  Headers request_headers_;
+  Headers response_headers_;
+
+  std::chrono::high_resolution_clock::time_point request_start_time_;
+
+  std::string request_method_;
+  std::string request_path_;
+  std::string request_http2_scheme_;
+  std::string request_http2_authority_;
+  // host we requested to downstream.  This is used to rewrite
+  // location header field to decide the location should be rewritten
+  // or not.
+  std::string request_downstream_host_;
+  std::string assembled_request_cookie_;
+
+  DefaultMemchunks request_buf_;
+  DefaultMemchunks response_buf_;
+
+  ev_timer upstream_rtimer_;
+  ev_timer upstream_wtimer_;
+
+  ev_timer downstream_rtimer_;
+  ev_timer downstream_wtimer_;
+
+  // the length of request body received so far
+  int64_t request_bodylen_;
+  // the length of response body received so far
+  int64_t response_bodylen_;
+
+  // the length of response body sent to upstream client
+  int64_t response_sent_bodylen_;
+
+  // content-length of request body, -1 if it is unknown.
+  int64_t request_content_length_;
+  // content-length of response body, -1 if it is unknown.
+  int64_t response_content_length_;
+
+  Upstream *upstream_;
+  std::unique_ptr<DownstreamConnection> dconn_;
+
+  size_t request_headers_sum_;
+  size_t response_headers_sum_;
+
+  // The number of bytes not consumed by the application yet.
+  size_t request_datalen_;
+  size_t response_datalen_;
+
+  size_t num_retry_;
+
+  int32_t stream_id_;
+  int32_t priority_;
+  // stream ID in backend connection
+  int32_t downstream_stream_id_;
+
+  // RST_STREAM error_code from downstream HTTP2 connection
+  uint32_t response_rst_stream_error_code_;
+
+  int request_state_;
+  int request_major_;
+  int request_minor_;
+
+  int response_state_;
+  unsigned int response_http_status_;
+  int response_major_;
+  int response_minor_;
+
+  http2::HeaderIndex request_hdidx_;
+  http2::HeaderIndex response_hdidx_;
+
+  // true if the request contains upgrade token (HTTP Upgrade or
+  // CONNECT)
+  bool upgrade_request_;
+  // true if the connection is upgraded (HTTP Upgrade or CONNECT)
+  bool upgraded_;
+
+  bool http2_upgrade_seen_;
+
+  bool chunked_request_;
+  bool request_connection_close_;
+  bool request_header_key_prev_;
+  bool request_http2_expect_body_;
+
+  bool chunked_response_;
+  bool response_connection_close_;
+  bool response_header_key_prev_;
+  bool expect_final_response_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_H
diff --git a/src/shrpx_downstream_connection.cc b/src/shrpx_downstream_connection.cc
new file mode 100644 (file)
index 0000000..77dcd44
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_connection.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection_pool.h"
+
+namespace shrpx {
+
+DownstreamConnection::DownstreamConnection(DownstreamConnectionPool *dconn_pool)
+    : dconn_pool_(dconn_pool), client_handler_(nullptr), downstream_(nullptr) {}
+
+DownstreamConnection::~DownstreamConnection() {}
+
+void DownstreamConnection::set_client_handler(ClientHandler *handler) {
+  client_handler_ = handler;
+}
+
+ClientHandler *DownstreamConnection::get_client_handler() {
+  return client_handler_;
+}
+
+Downstream *DownstreamConnection::get_downstream() { return downstream_; }
+
+DownstreamConnectionPool *DownstreamConnection::get_dconn_pool() const {
+  return dconn_pool_;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_connection.h b/src/shrpx_downstream_connection.h
new file mode 100644 (file)
index 0000000..5594ccf
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_CONNECTION_H
+#define SHRPX_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include "shrpx_io_control.h"
+
+namespace shrpx {
+
+class ClientHandler;
+class Upstream;
+class Downstream;
+class DownstreamConnectionPool;
+
+class DownstreamConnection {
+public:
+  DownstreamConnection(DownstreamConnectionPool *dconn_pool);
+  virtual ~DownstreamConnection();
+  virtual int attach_downstream(Downstream *downstream) = 0;
+  virtual void detach_downstream(Downstream *downstream) = 0;
+
+  virtual int push_request_headers() = 0;
+  virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen) = 0;
+  virtual int end_upload_data() = 0;
+
+  virtual void pause_read(IOCtrlReason reason) = 0;
+  virtual int resume_read(IOCtrlReason reason, size_t consumed) = 0;
+  virtual void force_resume_read() = 0;
+
+  virtual int on_read() = 0;
+  virtual int on_write() = 0;
+  virtual int on_timeout() { return 0; }
+
+  virtual void on_upstream_change(Upstream *uptream) = 0;
+  virtual int on_priority_change(int32_t pri) = 0;
+
+  void set_client_handler(ClientHandler *client_handler);
+  ClientHandler *get_client_handler();
+  Downstream *get_downstream();
+  DownstreamConnectionPool *get_dconn_pool() const;
+
+protected:
+  DownstreamConnectionPool *dconn_pool_;
+  ClientHandler *client_handler_;
+  Downstream *downstream_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_downstream_connection_pool.cc b/src/shrpx_downstream_connection_pool.cc
new file mode 100644 (file)
index 0000000..d762b77
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_downstream_connection.h"
+
+namespace shrpx {
+
+DownstreamConnectionPool::DownstreamConnectionPool() {}
+
+DownstreamConnectionPool::~DownstreamConnectionPool() {
+  for (auto dconn : pool_) {
+    delete dconn;
+  }
+}
+
+void DownstreamConnectionPool::add_downstream_connection(
+    std::unique_ptr<DownstreamConnection> dconn) {
+  pool_.insert(dconn.release());
+}
+
+std::unique_ptr<DownstreamConnection>
+DownstreamConnectionPool::pop_downstream_connection() {
+  if (pool_.empty()) {
+    return nullptr;
+  }
+
+  auto dconn = std::unique_ptr<DownstreamConnection>(*std::begin(pool_));
+  pool_.erase(std::begin(pool_));
+  return dconn;
+}
+
+void DownstreamConnectionPool::remove_downstream_connection(
+    DownstreamConnection *dconn) {
+  pool_.erase(dconn);
+  delete dconn;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_connection_pool.h b/src/shrpx_downstream_connection_pool.h
new file mode 100644 (file)
index 0000000..c2edce4
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_CONNECTION_POOL_H
+#define SHRPX_DOWNSTREAM_CONNECTION_POOL_H
+
+#include "shrpx.h"
+
+#include <memory>
+#include <set>
+
+namespace shrpx {
+
+class DownstreamConnection;
+
+class DownstreamConnectionPool {
+public:
+  DownstreamConnectionPool();
+  ~DownstreamConnectionPool();
+
+  void add_downstream_connection(std::unique_ptr<DownstreamConnection> dconn);
+  std::unique_ptr<DownstreamConnection> pop_downstream_connection();
+  void remove_downstream_connection(DownstreamConnection *dconn);
+
+private:
+  std::set<DownstreamConnection *> pool_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_CONNECTION_POOL_H
diff --git a/src/shrpx_downstream_queue.cc b/src/shrpx_downstream_queue.cc
new file mode 100644 (file)
index 0000000..1393aaf
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_queue.h"
+
+#include <cassert>
+#include <limits>
+
+#include "shrpx_downstream.h"
+
+namespace shrpx {
+
+DownstreamQueue::HostEntry::HostEntry() : num_active(0) {}
+
+DownstreamQueue::DownstreamQueue(size_t conn_max_per_host, bool unified_host)
+    : conn_max_per_host_(conn_max_per_host == 0
+                             ? std::numeric_limits<size_t>::max()
+                             : conn_max_per_host),
+      unified_host_(unified_host) {}
+
+DownstreamQueue::~DownstreamQueue() {}
+
+void DownstreamQueue::add_pending(std::unique_ptr<Downstream> downstream) {
+  auto stream_id = downstream->get_stream_id();
+  pending_downstreams_[stream_id] = std::move(downstream);
+}
+
+void DownstreamQueue::add_failure(std::unique_ptr<Downstream> downstream) {
+  auto stream_id = downstream->get_stream_id();
+  failure_downstreams_[stream_id] = std::move(downstream);
+}
+
+DownstreamQueue::HostEntry &
+DownstreamQueue::find_host_entry(const std::string &host) {
+  auto itr = host_entries_.find(host);
+  if (itr == std::end(host_entries_)) {
+#ifdef HAVE_STD_MAP_EMPLACE
+    std::tie(itr, std::ignore) = host_entries_.emplace(host, HostEntry());
+#else  // !HAVE_STD_MAP_EMPLACE
+    // for g++-4.7
+    std::tie(itr, std::ignore) = host_entries_.insert({host, HostEntry()});
+#endif // !HAVE_STD_MAP_EMPLACE
+  }
+  return (*itr).second;
+}
+
+const std::string &
+DownstreamQueue::make_host_key(const std::string &host) const {
+  static std::string empty_key;
+  return unified_host_ ? empty_key : host;
+}
+
+const std::string &
+DownstreamQueue::make_host_key(Downstream *downstream) const {
+  return make_host_key(downstream->get_request_http2_authority());
+}
+
+void DownstreamQueue::add_active(std::unique_ptr<Downstream> downstream) {
+  auto &ent = find_host_entry(make_host_key(downstream.get()));
+  ++ent.num_active;
+
+  auto stream_id = downstream->get_stream_id();
+  active_downstreams_[stream_id] = std::move(downstream);
+}
+
+void DownstreamQueue::add_blocked(std::unique_ptr<Downstream> downstream) {
+  auto &ent = find_host_entry(make_host_key(downstream.get()));
+  auto stream_id = downstream->get_stream_id();
+  ent.blocked.insert(stream_id);
+  blocked_downstreams_[stream_id] = std::move(downstream);
+}
+
+bool DownstreamQueue::can_activate(const std::string &host) const {
+  auto itr = host_entries_.find(make_host_key(host));
+  if (itr == std::end(host_entries_)) {
+    return true;
+  }
+  auto &ent = (*itr).second;
+  return ent.num_active < conn_max_per_host_;
+}
+
+namespace {
+std::unique_ptr<Downstream>
+pop_downstream(DownstreamQueue::DownstreamMap::iterator i,
+               DownstreamQueue::DownstreamMap &downstreams) {
+  auto downstream = std::move((*i).second);
+  downstreams.erase(i);
+  return downstream;
+}
+} // namespace
+
+namespace {
+bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent,
+                                DownstreamQueue::HostEntryMap &host_entries,
+                                const std::string &host) {
+  if (ent.blocked.empty() && ent.num_active == 0) {
+    host_entries.erase(host);
+    return true;
+  }
+  return false;
+}
+} // namespace
+
+std::unique_ptr<Downstream> DownstreamQueue::pop_pending(int32_t stream_id) {
+  auto itr = pending_downstreams_.find(stream_id);
+  if (itr == std::end(pending_downstreams_)) {
+    return nullptr;
+  }
+  return pop_downstream(itr, pending_downstreams_);
+}
+
+std::unique_ptr<Downstream>
+DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) {
+  auto kv = active_downstreams_.find(stream_id);
+
+  if (kv != std::end(active_downstreams_)) {
+    auto downstream = pop_downstream(kv, active_downstreams_);
+    auto &host = make_host_key(downstream.get());
+    auto &ent = find_host_entry(host);
+    --ent.num_active;
+
+    if (remove_host_entry_if_empty(ent, host_entries_, host)) {
+      return nullptr;
+    }
+
+    if (ent.blocked.empty() || ent.num_active >= conn_max_per_host_) {
+      return nullptr;
+    }
+
+    auto next_stream_id = *std::begin(ent.blocked);
+    ent.blocked.erase(std::begin(ent.blocked));
+
+    auto itr = blocked_downstreams_.find(next_stream_id);
+    assert(itr != std::end(blocked_downstreams_));
+
+    auto next_downstream = pop_downstream(itr, blocked_downstreams_);
+
+    remove_host_entry_if_empty(ent, host_entries_, host);
+
+    return next_downstream;
+  }
+
+  kv = blocked_downstreams_.find(stream_id);
+
+  if (kv != std::end(blocked_downstreams_)) {
+    auto downstream = pop_downstream(kv, blocked_downstreams_);
+    auto &host = make_host_key(downstream.get());
+    auto &ent = find_host_entry(host);
+    ent.blocked.erase(stream_id);
+
+    remove_host_entry_if_empty(ent, host_entries_, host);
+
+    return nullptr;
+  }
+
+  kv = pending_downstreams_.find(stream_id);
+
+  if (kv != std::end(pending_downstreams_)) {
+    pop_downstream(kv, pending_downstreams_);
+    return nullptr;
+  }
+
+  kv = failure_downstreams_.find(stream_id);
+
+  if (kv != std::end(failure_downstreams_)) {
+    pop_downstream(kv, failure_downstreams_);
+    return nullptr;
+  }
+
+  return nullptr;
+}
+
+Downstream *DownstreamQueue::find(int32_t stream_id) {
+  auto kv = active_downstreams_.find(stream_id);
+
+  if (kv != std::end(active_downstreams_)) {
+    return (*kv).second.get();
+  }
+
+  kv = blocked_downstreams_.find(stream_id);
+
+  if (kv != std::end(blocked_downstreams_)) {
+    return (*kv).second.get();
+  }
+
+  kv = pending_downstreams_.find(stream_id);
+
+  if (kv != std::end(pending_downstreams_)) {
+    return (*kv).second.get();
+  }
+
+  kv = failure_downstreams_.find(stream_id);
+
+  if (kv != std::end(failure_downstreams_)) {
+    return (*kv).second.get();
+  }
+
+  return nullptr;
+}
+
+const DownstreamQueue::DownstreamMap &
+DownstreamQueue::get_active_downstreams() const {
+  return active_downstreams_;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_queue.h b/src/shrpx_downstream_queue.h
new file mode 100644 (file)
index 0000000..17b5bce
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_QUEUE_H
+#define SHRPX_DOWNSTREAM_QUEUE_H
+
+#include "shrpx.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <set>
+#include <memory>
+
+namespace shrpx {
+
+class Downstream;
+
+class DownstreamQueue {
+public:
+  typedef std::map<int32_t, std::unique_ptr<Downstream>> DownstreamMap;
+
+  struct HostEntry {
+    // Set of stream ID that blocked by conn_max_per_host_.
+    std::set<int32_t> blocked;
+    // The number of connections currently made to this host.
+    size_t num_active;
+    HostEntry();
+  };
+
+  typedef std::map<std::string, HostEntry> HostEntryMap;
+
+  // conn_max_per_host == 0 means no limit for downstream connection.
+  DownstreamQueue(size_t conn_max_per_host = 0, bool unified_host = true);
+  ~DownstreamQueue();
+  void add_pending(std::unique_ptr<Downstream> downstream);
+  void add_failure(std::unique_ptr<Downstream> downstream);
+  // Adds |downstream| to active_downstreams_, which means that
+  // downstream connection has been started.
+  void add_active(std::unique_ptr<Downstream> downstream);
+  // Adds |downstream| to blocked_downstreams_, which means that
+  // download connection was blocked because conn_max_per_host_ limit.
+  void add_blocked(std::unique_ptr<Downstream> downstream);
+  // Returns true if we can make downstream connection to given
+  // |host|.
+  bool can_activate(const std::string &host) const;
+  // Removes pending Downstream object whose stream ID is |stream_id|
+  // from pending_downstreams_ and returns it.
+  std::unique_ptr<Downstream> pop_pending(int32_t stream_id);
+  // Removes Downstream object whose stream ID is |stream_id| from
+  // either pending_downstreams_, active_downstreams_,
+  // blocked_downstreams_ or failure_downstreams_.  If a Downstream
+  // object is removed from active_downstreams_, this function may
+  // return Downstream object with the same target host in
+  // blocked_downstreams_ if its connection is now not blocked by
+  // conn_max_per_host_ limit.
+  std::unique_ptr<Downstream> remove_and_pop_blocked(int32_t stream_id);
+  // Finds Downstream object denoted by |stream_id| either in
+  // pending_downstreams_, active_downstreams_, blocked_downstreams_
+  // or failure_downstreams_.
+  Downstream *find(int32_t stream_id);
+  const DownstreamMap &get_active_downstreams() const;
+  HostEntry &find_host_entry(const std::string &host);
+  const std::string &make_host_key(const std::string &host) const;
+  const std::string &make_host_key(Downstream *downstream) const;
+
+  // Maximum number of concurrent connections to the same host.
+  size_t conn_max_per_host_;
+
+private:
+  // Per target host structure to keep track of the number of
+  // connections to the same host.
+  std::map<std::string, HostEntry> host_entries_;
+  // Downstream objects, not processed yet
+  DownstreamMap pending_downstreams_;
+  // Downstream objects, failed to connect to downstream server
+  DownstreamMap failure_downstreams_;
+  // Downstream objects, downstream connection started
+  DownstreamMap active_downstreams_;
+  // Downstream objects, blocked by conn_max_per_host_
+  DownstreamMap blocked_downstreams_;
+  // true if downstream host is treated as the same.  Used for reverse
+  // proxying.
+  bool unified_host_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_QUEUE_H
diff --git a/src/shrpx_downstream_test.cc b/src/shrpx_downstream_test.cc
new file mode 100644 (file)
index 0000000..ad0abaa
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_downstream_test.h"
+
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_downstream.h"
+
+namespace shrpx {
+
+void test_downstream_index_request_headers(void) {
+  Downstream d(nullptr, 0, 0);
+  d.add_request_header("1", "0");
+  d.add_request_header("2", "1");
+  d.add_request_header("Charlie", "2");
+  d.add_request_header("Alpha", "3");
+  d.add_request_header("Delta", "4");
+  d.add_request_header("BravO", "5");
+  d.add_request_header(":method", "6");
+  d.add_request_header(":authority", "7");
+  d.index_request_headers();
+
+  auto ans = Headers{{"1", "0"},
+                     {"2", "1"},
+                     {"charlie", "2"},
+                     {"alpha", "3"},
+                     {"delta", "4"},
+                     {"bravo", "5"},
+                     {":method", "6"},
+                     {":authority", "7"}};
+  CU_ASSERT(ans == d.get_request_headers());
+}
+
+void test_downstream_index_response_headers(void) {
+  Downstream d(nullptr, 0, 0);
+  d.add_response_header("Charlie", "0");
+  d.add_response_header("Alpha", "1");
+  d.add_response_header("Delta", "2");
+  d.add_response_header("BravO", "3");
+  d.index_response_headers();
+
+  auto ans =
+      Headers{{"charlie", "0"}, {"alpha", "1"}, {"delta", "2"}, {"bravo", "3"}};
+  CU_ASSERT(ans == d.get_response_headers());
+}
+
+void test_downstream_get_request_header(void) {
+  Downstream d(nullptr, 0, 0);
+  d.add_request_header("alpha", "0");
+  d.add_request_header(":authority", "1");
+  d.add_request_header("content-length", "2");
+  d.index_request_headers();
+
+  // By token
+  CU_ASSERT(Header(":authority", "1") ==
+            *d.get_request_header(http2::HD__AUTHORITY));
+  CU_ASSERT(nullptr == d.get_request_header(http2::HD__METHOD));
+
+  // By name
+  CU_ASSERT(Header("alpha", "0") == *d.get_request_header("alpha"));
+  CU_ASSERT(nullptr == d.get_request_header("bravo"));
+}
+
+void test_downstream_get_response_header(void) {
+  Downstream d(nullptr, 0, 0);
+  d.add_response_header("alpha", "0");
+  d.add_response_header(":status", "1");
+  d.add_response_header("content-length", "2");
+  d.index_response_headers();
+
+  // By token
+  CU_ASSERT(Header(":status", "1") ==
+            *d.get_response_header(http2::HD__STATUS));
+  CU_ASSERT(nullptr == d.get_response_header(http2::HD__METHOD));
+}
+
+void test_downstream_crumble_request_cookie(void) {
+  Downstream d(nullptr, 0, 0);
+  d.add_request_header(":method", "get");
+  d.add_request_header(":path", "/");
+  auto val = "alpha; bravo; ; ;; charlie;;";
+  d.add_request_header(
+      reinterpret_cast<const uint8_t *>("cookie"), sizeof("cookie") - 1,
+      reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
+  d.add_request_header("cookie", ";delta");
+  d.add_request_header("cookie", "echo");
+  auto cookies = d.crumble_request_cookie();
+
+  Headers ans = {{"cookie", "alpha"},
+                 {"cookie", "bravo"},
+                 {"cookie", "charlie"},
+                 {"cookie", "delta"},
+                 {"cookie", "echo"}};
+  CU_ASSERT(ans == cookies);
+  CU_ASSERT(cookies[0].no_index);
+  CU_ASSERT(cookies[1].no_index);
+  CU_ASSERT(cookies[2].no_index);
+}
+
+void test_downstream_assemble_request_cookie(void) {
+  Downstream d(nullptr, 0, 0);
+  d.add_request_header(":method", "get");
+  d.add_request_header(":path", "/");
+  d.add_request_header("cookie", "alpha");
+  d.add_request_header("cookie", "bravo;");
+  d.add_request_header("cookie", "charlie; ");
+  d.add_request_header("cookie", "delta;;");
+  d.assemble_request_cookie();
+  CU_ASSERT("alpha; bravo; charlie; delta" == d.get_assembled_request_cookie());
+}
+
+void test_downstream_rewrite_location_response_header(void) {
+  {
+    Downstream d(nullptr, 0, 0);
+    d.set_request_downstream_host("localhost:3000");
+    d.add_request_header("host", "localhost");
+    d.add_response_header("location", "http://localhost:3000/");
+    d.index_request_headers();
+    d.index_response_headers();
+    d.rewrite_location_response_header("https");
+    auto location = d.get_response_header(http2::HD_LOCATION);
+    CU_ASSERT("https://localhost/" == (*location).value);
+  }
+  {
+    Downstream d(nullptr, 0, 0);
+    d.set_request_downstream_host("localhost");
+    d.set_request_http2_authority("localhost");
+    d.add_response_header("location", "http://localhost:3000/");
+    d.index_response_headers();
+    d.rewrite_location_response_header("https");
+    auto location = d.get_response_header(http2::HD_LOCATION);
+    CU_ASSERT("https://localhost/" == (*location).value);
+  }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_downstream_test.h b/src/shrpx_downstream_test.h
new file mode 100644 (file)
index 0000000..b1e1aee
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_DOWNSTREAM_TEST_H
+#define SHRPX_DOWNSTREAM_TEST_H
+
+namespace shrpx {
+
+void test_downstream_index_request_headers(void);
+void test_downstream_index_response_headers(void);
+void test_downstream_get_request_header(void);
+void test_downstream_get_response_header(void);
+void test_downstream_crumble_request_cookie(void);
+void test_downstream_assemble_request_cookie(void);
+void test_downstream_rewrite_location_response_header(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_DOWNSTREAM_TEST_H
diff --git a/src/shrpx_error.h b/src/shrpx_error.h
new file mode 100644 (file)
index 0000000..f1d1bff
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_ERROR_H
+#define SHRPX_ERROR_H
+
+#include "shrpx.h"
+
+namespace shrpx {
+
+// Deprecated, do not use.
+enum ErrorCode {
+  SHRPX_ERR_SUCCESS = 0,
+  SHRPX_ERR_ERROR = -1,
+  SHRPX_ERR_NETWORK = -100,
+  SHRPX_ERR_EOF = -101,
+  SHRPX_ERR_INPROGRESS = -102,
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_ERROR_H
diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc
new file mode 100644 (file)
index 0000000..7f2fcb6
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http.h"
+
+#include "shrpx_config.h"
+#include "shrpx_log.h"
+#include "shrpx_worker_config.h"
+#include "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace http {
+
+std::string create_error_html(unsigned int status_code) {
+  std::string res;
+  res.reserve(512);
+  auto status = http2::get_status_string(status_code);
+  res += "<!DOCTYPE html><html lang=en><title>";
+  res += status;
+  res += "</title><body><h1>";
+  res += status;
+  res += "</h1><footer>";
+  res += get_config()->server_name;
+  res += " at port ";
+  res += util::utos(get_config()->port);
+  res += "</footer></body></html>";
+  return res;
+}
+
+std::string create_via_header_value(int major, int minor) {
+  std::string hdrs;
+  hdrs += static_cast<char>(major + '0');
+  hdrs += ".";
+  hdrs += static_cast<char>(minor + '0');
+  hdrs += " nghttpx";
+  return hdrs;
+}
+
+std::string colorizeHeaders(const char *hdrs) {
+  std::string nhdrs;
+  const char *p = strchr(hdrs, '\n');
+  if (!p) {
+    // Not valid HTTP header
+    return hdrs;
+  }
+  nhdrs.append(hdrs, p + 1);
+  ++p;
+  while (1) {
+    const char *np = strchr(p, ':');
+    if (!np) {
+      nhdrs.append(p);
+      break;
+    }
+    nhdrs += TTY_HTTP_HD;
+    nhdrs.append(p, np);
+    nhdrs += TTY_RST;
+    p = np;
+    np = strchr(p, '\n');
+    if (!np) {
+      nhdrs.append(p);
+      break;
+    }
+    nhdrs.append(p, np + 1);
+    p = np + 1;
+  }
+  return nhdrs;
+}
+
+ssize_t select_padding_callback(nghttp2_session *session,
+                                const nghttp2_frame *frame, size_t max_payload,
+                                void *user_data) {
+  return std::min(max_payload, frame->hd.length + get_config()->padding);
+}
+
+} // namespace http
+
+} // namespace shrpx
diff --git a/src/shrpx_http.h b/src/shrpx_http.h
new file mode 100644 (file)
index 0000000..65dbe0e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP_H
+#define SHRPX_HTTP_H
+
+#include "shrpx.h"
+
+#include <string>
+
+#include <nghttp2/nghttp2.h>
+
+namespace shrpx {
+
+namespace http {
+
+std::string create_error_html(unsigned int status_code);
+
+std::string create_via_header_value(int major, int minor);
+
+// Adds ANSI color codes to HTTP headers |hdrs|.
+std::string colorizeHeaders(const char *hdrs);
+
+ssize_t select_padding_callback(nghttp2_session *session,
+                                const nghttp2_frame *frame, size_t max_payload,
+                                void *user_data);
+
+} // namespace http
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_H
diff --git a/src/shrpx_http2_downstream_connection.cc b/src/shrpx_http2_downstream_connection.cc
new file mode 100644 (file)
index 0000000..74bdcc8
--- /dev/null
@@ -0,0 +1,587 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http2_downstream_connection.h"
+
+#include <unistd.h>
+
+#include "http-parser/http_parser.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_http.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_worker_config.h"
+#include "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+Http2DownstreamConnection::Http2DownstreamConnection(
+    DownstreamConnectionPool *dconn_pool, Http2Session *http2session)
+    : DownstreamConnection(dconn_pool), http2session_(http2session),
+      sd_(nullptr) {}
+
+Http2DownstreamConnection::~Http2DownstreamConnection() {
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, this) << "Deleting";
+  }
+  if (downstream_) {
+    downstream_->disable_downstream_rtimer();
+    downstream_->disable_downstream_wtimer();
+
+    uint32_t error_code;
+    if (downstream_->get_request_state() == Downstream::STREAM_CLOSED &&
+        downstream_->get_upgraded()) {
+      // For upgraded connection, send NO_ERROR.  Should we consider
+      // request states other than Downstream::STREAM_CLOSED ?
+      error_code = NGHTTP2_NO_ERROR;
+    } else {
+      error_code = NGHTTP2_INTERNAL_ERROR;
+    }
+
+    if (downstream_->get_downstream_stream_id() != -1) {
+      if (LOG_ENABLED(INFO)) {
+        DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream_
+                          << ", stream_id="
+                          << downstream_->get_downstream_stream_id()
+                          << ", error_code=" << error_code;
+      }
+
+      submit_rst_stream(downstream_, error_code);
+
+      http2session_->consume(downstream_->get_downstream_stream_id(),
+                             downstream_->get_response_datalen());
+
+      downstream_->reset_response_datalen();
+
+      http2session_->signal_write();
+    }
+  }
+  http2session_->remove_downstream_connection(this);
+  // Downstream and DownstreamConnection may be deleted
+  // asynchronously.
+  if (downstream_) {
+    downstream_->release_downstream_connection();
+  }
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, this) << "Deleted";
+  }
+}
+
+int Http2DownstreamConnection::attach_downstream(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+  }
+  http2session_->add_downstream_connection(this);
+  if (http2session_->get_state() == Http2Session::DISCONNECTED) {
+    http2session_->signal_write();
+  }
+
+  downstream_ = downstream;
+  downstream_->reset_downstream_rtimer();
+
+  return 0;
+}
+
+void Http2DownstreamConnection::detach_downstream(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+  }
+  if (submit_rst_stream(downstream) == 0) {
+    http2session_->signal_write();
+  }
+
+  if (downstream_->get_downstream_stream_id() != -1) {
+    http2session_->consume(downstream_->get_downstream_stream_id(),
+                           downstream_->get_response_datalen());
+
+    downstream_->reset_response_datalen();
+
+    http2session_->signal_write();
+  }
+
+  downstream->disable_downstream_rtimer();
+  downstream->disable_downstream_wtimer();
+  downstream_ = nullptr;
+}
+
+int Http2DownstreamConnection::submit_rst_stream(Downstream *downstream,
+                                                 uint32_t error_code) {
+  int rv = -1;
+  if (http2session_->get_state() == Http2Session::CONNECTED &&
+      downstream->get_downstream_stream_id() != -1) {
+    switch (downstream->get_response_state()) {
+    case Downstream::MSG_RESET:
+    case Downstream::MSG_BAD_HEADER:
+    case Downstream::MSG_COMPLETE:
+      break;
+    default:
+      if (LOG_ENABLED(INFO)) {
+        DCLOG(INFO, this) << "Submit RST_STREAM for DOWNSTREAM:" << downstream
+                          << ", stream_id="
+                          << downstream->get_downstream_stream_id();
+      }
+      rv = http2session_->submit_rst_stream(
+          downstream->get_downstream_stream_id(), error_code);
+    }
+  }
+  return rv;
+}
+
+namespace {
+ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
+                                 uint8_t *buf, size_t length,
+                                 uint32_t *data_flags,
+                                 nghttp2_data_source *source, void *user_data) {
+  auto sd = static_cast<StreamData *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+  if (!sd || !sd->dconn) {
+    return NGHTTP2_ERR_DEFERRED;
+  }
+  auto dconn = static_cast<Http2DownstreamConnection *>(source->ptr);
+  auto downstream = dconn->get_downstream();
+  if (!downstream) {
+    // In this case, RST_STREAM should have been issued. But depending
+    // on the priority, DATA frame may come first.
+    return NGHTTP2_ERR_DEFERRED;
+  }
+  auto input = downstream->get_request_buf();
+  auto nread = input->remove(buf, length);
+  auto input_empty = input->rleft() == 0;
+
+  if (nread > 0) {
+    // This is important because it will handle flow control
+    // stuff.
+    if (downstream->get_upstream()->resume_read(SHRPX_NO_BUFFER, downstream,
+                                                nread) != 0) {
+      // In this case, downstream may be deleted.
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    // Check dconn is still alive because Upstream::resume_read()
+    // may delete downstream which will delete dconn.
+    if (sd->dconn == nullptr) {
+      return NGHTTP2_ERR_DEFERRED;
+    }
+  }
+
+  if (input_empty &&
+      downstream->get_request_state() == Downstream::MSG_COMPLETE &&
+      // If connection is upgraded, don't set EOF flag, since HTTP/1
+      // will set MSG_COMPLETE to request state after upgrade response
+      // header is seen.
+      (!downstream->get_upgrade_request() ||
+       (downstream->get_response_state() == Downstream::HEADER_COMPLETE &&
+        !downstream->get_upgraded()))) {
+
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  }
+
+  if (!input_empty) {
+    downstream->reset_downstream_wtimer();
+  } else {
+    downstream->disable_downstream_wtimer();
+  }
+
+  if (nread == 0 && (*data_flags & NGHTTP2_DATA_FLAG_EOF) == 0) {
+    downstream->disable_downstream_wtimer();
+
+    return NGHTTP2_ERR_DEFERRED;
+  }
+
+  return nread;
+}
+} // namespace
+
+int Http2DownstreamConnection::push_request_headers() {
+  int rv;
+  if (!http2session_->can_push_request()) {
+    // The HTTP2 session to the backend has not been established or
+    // connection is now being checked.  This function will be called
+    // again just after it is established.
+    http2session_->start_checking_connection();
+    return 0;
+  }
+  if (!downstream_) {
+    return 0;
+  }
+
+  const char *authority = nullptr, *host = nullptr;
+  if (!get_config()->no_host_rewrite && !get_config()->http2_proxy &&
+      !get_config()->client_proxy) {
+    // HTTP/2 backend does not support multiple address, so we always
+    // use index = 0.
+    if (!downstream_->get_request_http2_authority().empty()) {
+      authority = get_config()->downstream_addrs[0].hostport.get();
+    }
+    if (downstream_->get_request_header(http2::HD_HOST)) {
+      host = get_config()->downstream_addrs[0].hostport.get();
+    }
+  } else {
+    if (!downstream_->get_request_http2_authority().empty()) {
+      authority = downstream_->get_request_http2_authority().c_str();
+    }
+    auto h = downstream_->get_request_header(http2::HD_HOST);
+    if (h) {
+      host = h->value.c_str();
+    }
+  }
+
+  if (!authority && !host) {
+    // upstream is HTTP/1.0.  We use backend server's host
+    // nonetheless.
+    host = get_config()->downstream_addrs[0].hostport.get();
+  }
+
+  if (authority) {
+    downstream_->set_request_downstream_host(authority);
+  } else {
+    downstream_->set_request_downstream_host(host);
+  }
+
+  size_t nheader = downstream_->get_request_headers().size();
+
+  Headers cookies;
+  if (!get_config()->http2_no_cookie_crumbling) {
+    cookies = downstream_->crumble_request_cookie();
+  }
+
+  // 8 means:
+  // 1. :method
+  // 2. :scheme
+  // 3. :path
+  // 4. :authority (at least either :authority or host exists)
+  // 5. host
+  // 6. via (optional)
+  // 7. x-forwarded-for (optional)
+  // 8. x-forwarded-proto (optional)
+  auto nva = std::vector<nghttp2_nv>();
+  nva.reserve(nheader + 8 + cookies.size());
+
+  std::string via_value;
+  std::string xff_value;
+  std::string scheme, uri_authority, path, query;
+
+  // To reconstruct HTTP/1 status line and headers, proxy should
+  // preserve host header field. See draft-09 section 8.1.3.1.
+  if (downstream_->get_request_method() == "CONNECT") {
+    // The upstream may be HTTP/2 or HTTP/1
+    if (authority) {
+      nva.push_back(http2::make_nv_lc(":authority", authority));
+    } else {
+      nva.push_back(
+          http2::make_nv_ls(":authority", downstream_->get_request_path()));
+    }
+  } else if (!downstream_->get_request_http2_scheme().empty()) {
+    // Here the upstream is HTTP/2
+    nva.push_back(
+        http2::make_nv_ls(":scheme", downstream_->get_request_http2_scheme()));
+    nva.push_back(http2::make_nv_ls(":path", downstream_->get_request_path()));
+    if (authority) {
+      nva.push_back(http2::make_nv_lc(":authority", authority));
+    }
+  } else {
+    // The upstream is HTTP/1
+    http_parser_url u;
+    const char *url = downstream_->get_request_path().c_str();
+    memset(&u, 0, sizeof(u));
+    rv = http_parser_parse_url(url, downstream_->get_request_path().size(), 0,
+                               &u);
+    if (rv == 0) {
+      http2::copy_url_component(scheme, &u, UF_SCHEMA, url);
+      http2::copy_url_component(uri_authority, &u, UF_HOST, url);
+      http2::copy_url_component(path, &u, UF_PATH, url);
+      http2::copy_url_component(query, &u, UF_QUERY, url);
+      if (path.empty()) {
+        if (!uri_authority.empty() &&
+            downstream_->get_request_method() == "OPTIONS") {
+          path = "*";
+        } else {
+          path = "/";
+        }
+      }
+      if (!query.empty()) {
+        path += "?";
+        path += query;
+      }
+    }
+    if (scheme.empty()) {
+      if (client_handler_->get_ssl()) {
+        nva.push_back(http2::make_nv_ll(":scheme", "https"));
+      } else {
+        nva.push_back(http2::make_nv_ll(":scheme", "http"));
+      }
+    } else {
+      nva.push_back(http2::make_nv_ls(":scheme", scheme));
+    }
+    if (path.empty()) {
+      nva.push_back(
+          http2::make_nv_ls(":path", downstream_->get_request_path()));
+    } else {
+      nva.push_back(http2::make_nv_ls(":path", path));
+    }
+
+    if (!get_config()->no_host_rewrite && !get_config()->http2_proxy &&
+        !get_config()->client_proxy) {
+      if (authority) {
+        nva.push_back(http2::make_nv_lc(":authority", authority));
+      }
+    } else if (!uri_authority.empty()) {
+      // TODO properly check IPv6 numeric address
+      if (uri_authority.find(":") != std::string::npos) {
+        uri_authority = "[" + uri_authority;
+        uri_authority += "]";
+      }
+      if (u.field_set & (1 << UF_PORT)) {
+        uri_authority += ":";
+        uri_authority += util::utos(u.port);
+      }
+      nva.push_back(http2::make_nv_ls(":authority", uri_authority));
+    }
+  }
+
+  nva.push_back(
+      http2::make_nv_ls(":method", downstream_->get_request_method()));
+
+  if (host) {
+    nva.push_back(http2::make_nv_lc("host", host));
+  }
+
+  http2::copy_headers_to_nva(nva, downstream_->get_request_headers());
+
+  bool chunked_encoding = false;
+  auto transfer_encoding =
+      downstream_->get_request_header(http2::HD_TRANSFER_ENCODING);
+  if (transfer_encoding &&
+      util::strieq((*transfer_encoding).value.c_str(), "chunked")) {
+    chunked_encoding = true;
+  }
+
+  for (auto &nv : cookies) {
+    nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
+  }
+
+  auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
+  if (get_config()->add_x_forwarded_for) {
+    if (xff && !get_config()->strip_incoming_x_forwarded_for) {
+      xff_value = (*xff).value;
+      xff_value += ", ";
+    }
+    xff_value +=
+        downstream_->get_upstream()->get_client_handler()->get_ipaddr();
+    nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
+  } else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
+    nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value));
+  }
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy &&
+      downstream_->get_request_method() != "CONNECT") {
+    // We use same protocol with :scheme header field
+    if (scheme.empty()) {
+      if (client_handler_->get_ssl()) {
+        nva.push_back(http2::make_nv_ll("x-forwarded-proto", "https"));
+      } else {
+        nva.push_back(http2::make_nv_ll("x-forwarded-proto", "http"));
+      }
+    } else {
+      nva.push_back(http2::make_nv_ls("x-forwarded-proto", scheme));
+    }
+  }
+
+  auto via = downstream_->get_request_header(http2::HD_VIA);
+  if (get_config()->no_via) {
+    if (via) {
+      nva.push_back(http2::make_nv_ls("via", (*via).value));
+    }
+  } else {
+    if (via) {
+      via_value = (*via).value;
+      via_value += ", ";
+    }
+    via_value += http::create_via_header_value(
+        downstream_->get_request_major(), downstream_->get_request_minor());
+    nva.push_back(http2::make_nv_ls("via", via_value));
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    std::stringstream ss;
+    for (auto &nv : nva) {
+      ss << TTY_HTTP_HD;
+      ss.write(reinterpret_cast<const char *>(nv.name), nv.namelen);
+      ss << TTY_RST << ": ";
+      ss.write(reinterpret_cast<const char *>(nv.value), nv.valuelen);
+      ss << "\n";
+    }
+    DCLOG(INFO, this) << "HTTP request headers\n" << ss.str();
+  }
+
+  auto content_length =
+      downstream_->get_request_header(http2::HD_CONTENT_LENGTH);
+  // TODO check content-length: 0 case
+
+  if (downstream_->get_request_method() == "CONNECT" || chunked_encoding ||
+      content_length || downstream_->get_request_http2_expect_body()) {
+    // Request-body is expected.
+    nghttp2_data_provider data_prd;
+    data_prd.source.ptr = this;
+    data_prd.read_callback = http2_data_read_callback;
+    rv = http2session_->submit_request(this, downstream_->get_priority(),
+                                       nva.data(), nva.size(), &data_prd);
+  } else {
+    rv = http2session_->submit_request(this, downstream_->get_priority(),
+                                       nva.data(), nva.size(), nullptr);
+  }
+  if (rv != 0) {
+    DCLOG(FATAL, this) << "nghttp2_submit_request() failed";
+    return -1;
+  }
+
+  downstream_->reset_downstream_wtimer();
+
+  http2session_->signal_write();
+  return 0;
+}
+
+int Http2DownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+                                                      size_t datalen) {
+  int rv;
+  auto output = downstream_->get_request_buf();
+  output->append(data, datalen);
+  if (downstream_->get_downstream_stream_id() != -1) {
+    rv = http2session_->resume_data(this);
+    if (rv != 0) {
+      return -1;
+    }
+
+    downstream_->ensure_downstream_wtimer();
+
+    http2session_->signal_write();
+  }
+  return 0;
+}
+
+int Http2DownstreamConnection::end_upload_data() {
+  int rv;
+  if (downstream_->get_downstream_stream_id() != -1) {
+    rv = http2session_->resume_data(this);
+    if (rv != 0) {
+      return -1;
+    }
+
+    downstream_->ensure_downstream_wtimer();
+
+    http2session_->signal_write();
+  }
+  return 0;
+}
+
+int Http2DownstreamConnection::resume_read(IOCtrlReason reason,
+                                           size_t consumed) {
+  int rv;
+
+  if (http2session_->get_state() != Http2Session::CONNECTED ||
+      !http2session_->get_flow_control()) {
+    return 0;
+  }
+
+  if (!downstream_ || downstream_->get_downstream_stream_id() == -1) {
+    return 0;
+  }
+
+  if (consumed > 0) {
+    assert(downstream_->get_response_datalen() >= consumed);
+
+    rv = http2session_->consume(downstream_->get_downstream_stream_id(),
+                                consumed);
+
+    if (rv != 0) {
+      return -1;
+    }
+
+    downstream_->dec_response_datalen(consumed);
+
+    http2session_->signal_write();
+  }
+
+  return 0;
+}
+
+int Http2DownstreamConnection::on_read() { return 0; }
+
+int Http2DownstreamConnection::on_write() { return 0; }
+
+void Http2DownstreamConnection::attach_stream_data(StreamData *sd) {
+  // It is possible sd->dconn is not NULL. sd is detached when
+  // on_stream_close_callback. Before that, after MSG_COMPLETE is set
+  // to Downstream::set_response_state(), upstream's readcb is called
+  // and execution path eventually could reach here. Since the
+  // response was already handled, we just detach sd.
+  detach_stream_data();
+  sd_ = sd;
+  sd_->dconn = this;
+}
+
+StreamData *Http2DownstreamConnection::detach_stream_data() {
+  if (sd_) {
+    auto sd = sd_;
+    sd_ = nullptr;
+    sd->dconn = nullptr;
+    return sd;
+  }
+  return nullptr;
+}
+
+int Http2DownstreamConnection::on_priority_change(int32_t pri) {
+  int rv;
+  if (downstream_->get_priority() == pri) {
+    return 0;
+  }
+  downstream_->set_priority(pri);
+  if (http2session_->get_state() != Http2Session::CONNECTED) {
+    return 0;
+  }
+  rv = http2session_->submit_priority(this, pri);
+  if (rv != 0) {
+    DLOG(FATAL, this) << "nghttp2_submit_priority() failed";
+    return -1;
+  }
+  http2session_->signal_write();
+  return 0;
+}
+
+int Http2DownstreamConnection::on_timeout() {
+  if (!downstream_) {
+    return 0;
+  }
+
+  return submit_rst_stream(downstream_, NGHTTP2_NO_ERROR);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_downstream_connection.h b/src/shrpx_http2_downstream_connection.h
new file mode 100644 (file)
index 0000000..cee6265
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include <openssl/ssl.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_downstream_connection.h"
+
+namespace shrpx {
+
+struct StreamData;
+class Http2Session;
+class DownstreamConnectionPool;
+
+class Http2DownstreamConnection : public DownstreamConnection {
+public:
+  Http2DownstreamConnection(DownstreamConnectionPool *dconn_pool,
+                            Http2Session *http2session);
+  virtual ~Http2DownstreamConnection();
+  virtual int attach_downstream(Downstream *downstream);
+  virtual void detach_downstream(Downstream *downstream);
+
+  virtual int push_request_headers();
+  virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+  virtual int end_upload_data();
+
+  virtual void pause_read(IOCtrlReason reason) {}
+  virtual int resume_read(IOCtrlReason reason, size_t consumed);
+  virtual void force_resume_read() {}
+
+  virtual int on_read();
+  virtual int on_write();
+  virtual int on_timeout();
+
+  virtual void on_upstream_change(Upstream *upstream) {}
+  virtual int on_priority_change(int32_t pri);
+
+  int send();
+
+  void attach_stream_data(StreamData *sd);
+  StreamData *detach_stream_data();
+
+  int submit_rst_stream(Downstream *downstream,
+                        uint32_t error_code = NGHTTP2_INTERNAL_ERROR);
+
+private:
+  Http2Session *http2session_;
+  StreamData *sd_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_http2_session.cc b/src/shrpx_http2_session.cc
new file mode 100644 (file)
index 0000000..51e84f3
--- /dev/null
@@ -0,0 +1,1689 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http2_session.h"
+
+#include <netinet/tcp.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include <openssl/err.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_http2_downstream_connection.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_ssl.h"
+#include "shrpx_http.h"
+#include "shrpx_worker_config.h"
+#include "http2.h"
+#include "util.h"
+#include "base64.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void connchk_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto http2session = static_cast<Http2Session *>(w->data);
+  SSLOG(INFO, http2session) << "connection check required";
+  ev_timer_stop(loop, w);
+  http2session->set_connection_check_state(
+      Http2Session::CONNECTION_CHECK_REQUIRED);
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto http2session = static_cast<Http2Session *>(w->data);
+  http2session->stop_settings_timer();
+  SSLOG(INFO, http2session) << "SETTINGS timeout";
+  if (http2session->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
+    http2session->disconnect();
+    return;
+  }
+  http2session->signal_write();
+}
+} // namespace
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto http2session = static_cast<Http2Session *>(conn->data);
+
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, http2session) << "Timeout";
+  }
+
+  http2session->disconnect(http2session->get_state() ==
+                           Http2Session::CONNECTING);
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  int rv;
+  auto conn = static_cast<Connection *>(w->data);
+  auto http2session = static_cast<Http2Session *>(conn->data);
+  http2session->connection_alive();
+  rv = http2session->do_read();
+  if (rv != 0) {
+    http2session->disconnect(http2session->should_hard_fail());
+  }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+  int rv;
+  auto conn = static_cast<Connection *>(w->data);
+  auto http2session = static_cast<Http2Session *>(conn->data);
+  http2session->clear_write_request();
+  http2session->connection_alive();
+  rv = http2session->do_write();
+  if (rv != 0) {
+    http2session->disconnect(http2session->should_hard_fail());
+  }
+}
+} // namespace
+
+namespace {
+void wrschedcb(struct ev_loop *loop, ev_prepare *w, int revents) {
+  auto http2session = static_cast<Http2Session *>(w->data);
+  if (!http2session->write_requested()) {
+    return;
+  }
+  http2session->clear_write_request();
+  switch (http2session->get_state()) {
+  case Http2Session::DISCONNECTED:
+    LOG(INFO) << "wrschedcb start connect";
+    if (http2session->initiate_connection() != 0) {
+      SSLOG(FATAL, http2session) << "Could not initiate backend connection";
+      http2session->disconnect(true);
+    }
+    break;
+  case Http2Session::CONNECTED:
+    writecb(loop, http2session->get_wev(), revents);
+    break;
+  }
+}
+} // namespace
+
+Http2Session::Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx)
+    : conn_(loop, -1, nullptr, get_config()->downstream_write_timeout,
+            get_config()->downstream_read_timeout, 0, 0, 0, 0, writecb, readcb,
+            timeoutcb, this),
+      ssl_ctx_(ssl_ctx), session_(nullptr), data_pending_(nullptr),
+      data_pendinglen_(0), state_(DISCONNECTED),
+      connection_check_state_(CONNECTION_CHECK_NONE), flow_control_(false),
+      write_requested_(false) {
+
+  read_ = write_ = &Http2Session::noop;
+  on_read_ = on_write_ = &Http2Session::noop;
+
+  // We will resuse this many times, so use repeat timeout value.
+  ev_timer_init(&connchk_timer_, connchk_timeout_cb, 0., 5.);
+
+  connchk_timer_.data = this;
+
+  // SETTINGS ACK timeout is 10 seconds for now.  We will resuse this
+  // many times, so use repeat timeout value.
+  ev_timer_init(&settings_timer_, settings_timeout_cb, 0., 10.);
+
+  settings_timer_.data = this;
+
+  ev_prepare_init(&wrsched_prep_, &wrschedcb);
+  wrsched_prep_.data = this;
+
+  ev_prepare_start(conn_.loop, &wrsched_prep_);
+}
+
+Http2Session::~Http2Session() { disconnect(); }
+
+int Http2Session::disconnect(bool hard) {
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, this) << "Disconnecting";
+  }
+  nghttp2_session_del(session_);
+  session_ = nullptr;
+
+  rb_.reset();
+  wb_.reset();
+
+  ev_timer_stop(conn_.loop, &settings_timer_);
+  ev_timer_stop(conn_.loop, &connchk_timer_);
+
+  read_ = write_ = &Http2Session::noop;
+  on_read_ = on_write_ = &Http2Session::noop;
+
+  conn_.disconnect();
+
+  if (proxy_htp_) {
+    proxy_htp_.reset();
+  }
+
+  connection_check_state_ = CONNECTION_CHECK_NONE;
+  state_ = DISCONNECTED;
+
+  // Delete all client handler associated to Downstream. When deleting
+  // Http2DownstreamConnection, it calls this object's
+  // remove_downstream_connection(). The multiple
+  // Http2DownstreamConnection objects belong to the same
+  // ClientHandler object. So first dump ClientHandler objects.  We
+  // want to allow creating new pending Http2DownstreamConnection with
+  // this object.  In order to achieve this, we first swap dconns_ and
+  // streams_.  Upstream::on_downstream_reset() may add
+  // Http2DownstreamConnection.
+  std::set<Http2DownstreamConnection *> dconns;
+  dconns.swap(dconns_);
+  std::set<StreamData *> streams;
+  streams.swap(streams_);
+
+  std::set<ClientHandler *> handlers;
+  for (auto dc : dconns) {
+    if (!dc->get_client_handler()) {
+      continue;
+    }
+    handlers.insert(dc->get_client_handler());
+  }
+  for (auto h : handlers) {
+    if (h->get_upstream()->on_downstream_reset(hard) != 0) {
+      delete h;
+    }
+  }
+
+  for (auto &s : streams) {
+    delete s;
+  }
+
+  return 0;
+}
+
+int Http2Session::check_cert() { return ssl::check_cert(conn_.tls.ssl); }
+
+int Http2Session::initiate_connection() {
+  int rv = 0;
+  if (get_config()->downstream_http_proxy_host && state_ == DISCONNECTED) {
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, this) << "Connecting to the proxy "
+                        << get_config()->downstream_http_proxy_host.get() << ":"
+                        << get_config()->downstream_http_proxy_port;
+    }
+
+    conn_.fd = util::create_nonblock_socket(
+        get_config()->downstream_http_proxy_addr.storage.ss_family);
+
+    if (conn_.fd == -1) {
+      return -1;
+    }
+
+    rv = connect(conn_.fd, const_cast<sockaddr *>(
+                               &get_config()->downstream_http_proxy_addr.sa),
+                 get_config()->downstream_http_proxy_addrlen);
+    if (rv != 0 && errno != EINPROGRESS) {
+      SSLOG(ERROR, this) << "Failed to connect to the proxy "
+                         << get_config()->downstream_http_proxy_host.get()
+                         << ":" << get_config()->downstream_http_proxy_port;
+      return -1;
+    }
+
+    ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+    ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+
+    conn_.wlimit.startw();
+
+    // TODO we should have timeout for connection establishment
+    ev_timer_again(conn_.loop, &conn_.wt);
+
+    write_ = &Http2Session::connected;
+
+    on_read_ = &Http2Session::downstream_read_proxy;
+    on_write_ = &Http2Session::downstream_connect_proxy;
+
+    proxy_htp_ = make_unique<http_parser>();
+    http_parser_init(proxy_htp_.get(), HTTP_RESPONSE);
+    proxy_htp_->data = this;
+
+    state_ = PROXY_CONNECTING;
+
+    return 0;
+  }
+
+  if (state_ == DISCONNECTED || state_ == PROXY_CONNECTED) {
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, this) << "Connecting to downstream server";
+    }
+    if (ssl_ctx_) {
+      // We are establishing TLS connection.
+      conn_.tls.ssl = SSL_new(ssl_ctx_);
+      if (!conn_.tls.ssl) {
+        SSLOG(ERROR, this) << "SSL_new() failed: "
+                           << ERR_error_string(ERR_get_error(), NULL);
+        return -1;
+      }
+
+      const char *sni_name = nullptr;
+      if (get_config()->backend_tls_sni_name) {
+        sni_name = get_config()->backend_tls_sni_name.get();
+      } else {
+        sni_name = get_config()->downstream_addrs[0].host.get();
+      }
+
+      if (sni_name && !util::numeric_host(sni_name)) {
+        // TLS extensions: SNI. There is no documentation about the return
+        // code for this function (actually this is macro wrapping SSL_ctrl
+        // at the time of this writing).
+        SSL_set_tlsext_host_name(conn_.tls.ssl, sni_name);
+      }
+      // If state_ == PROXY_CONNECTED, we has connected to the proxy
+      // using conn_.fd and tunnel has been established.
+      if (state_ == DISCONNECTED) {
+        assert(conn_.fd == -1);
+
+        conn_.fd = util::create_nonblock_socket(
+            get_config()->downstream_addrs[0].addr.storage.ss_family);
+        if (conn_.fd == -1) {
+          return -1;
+        }
+
+        rv = connect(
+            conn_.fd,
+            // TODO maybe not thread-safe?
+            const_cast<sockaddr *>(&get_config()->downstream_addrs[0].addr.sa),
+            get_config()->downstream_addrs[0].addrlen);
+        if (rv != 0 && errno != EINPROGRESS) {
+          return -1;
+        }
+      }
+
+      if (SSL_set_fd(conn_.tls.ssl, conn_.fd) == 0) {
+        return -1;
+      }
+
+      SSL_set_connect_state(conn_.tls.ssl);
+    } else {
+      if (state_ == DISCONNECTED) {
+        // Without TLS and proxy.
+        assert(conn_.fd == -1);
+
+        conn_.fd = util::create_nonblock_socket(
+            get_config()->downstream_addrs[0].addr.storage.ss_family);
+
+        if (conn_.fd == -1) {
+          return -1;
+        }
+
+        rv = connect(conn_.fd, const_cast<sockaddr *>(
+                                   &get_config()->downstream_addrs[0].addr.sa),
+                     get_config()->downstream_addrs[0].addrlen);
+        if (rv != 0 && errno != EINPROGRESS) {
+          return -1;
+        }
+      } else {
+        // Without TLS but with proxy.  Connection already
+        // established.
+        if (on_connect() != -1) {
+          state_ = CONNECT_FAILING;
+          return -1;
+        }
+      }
+    }
+
+    // rev_ and wev_ could possibly be active here.  Since calling
+    // ev_io_set is not allowed while watcher is active, we have to
+    // stop them just in case.
+    conn_.rlimit.stopw();
+    conn_.wlimit.stopw();
+
+    ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+    ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+
+    write_ = &Http2Session::connected;
+
+    on_write_ = &Http2Session::downstream_write;
+    on_read_ = &Http2Session::downstream_read;
+
+    // We have been already connected when no TLS and proxy is used.
+    if (state_ != CONNECTED) {
+      state_ = CONNECTING;
+      conn_.wlimit.startw();
+      // TODO we should have timeout for connection establishment
+      ev_timer_again(conn_.loop, &conn_.wt);
+    } else {
+      conn_.rlimit.startw();
+      ev_timer_again(conn_.loop, &conn_.rt);
+    }
+
+    return 0;
+  }
+
+  // Unreachable
+  DIE();
+  return 0;
+}
+
+namespace {
+int htp_hdrs_completecb(http_parser *htp) {
+  auto http2session = static_cast<Http2Session *>(htp->data);
+  // We just check status code here
+  if (htp->status_code == 200) {
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, http2session) << "Tunneling success";
+    }
+    http2session->set_state(Http2Session::PROXY_CONNECTED);
+
+    return 0;
+  }
+
+  SSLOG(WARN, http2session) << "Tunneling failed: " << htp->status_code;
+  http2session->set_state(Http2Session::PROXY_FAILED);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+http_parser_settings htp_hooks = {
+    nullptr,             // http_cb      on_message_begin;
+    nullptr,             // http_data_cb on_url;
+    nullptr,             // http_data_cb on_status;
+    nullptr,             // http_data_cb on_header_field;
+    nullptr,             // http_data_cb on_header_value;
+    htp_hdrs_completecb, // http_cb      on_headers_complete;
+    nullptr,             // http_data_cb on_body;
+    nullptr              // http_cb      on_message_complete;
+};
+} // namespace
+
+int Http2Session::downstream_read_proxy() {
+  for (;;) {
+    if (rb_.rleft() == 0) {
+      return 0;
+    }
+
+    size_t nread = http_parser_execute(proxy_htp_.get(), &htp_hooks,
+                                       reinterpret_cast<const char *>(rb_.pos),
+                                       rb_.rleft());
+
+    rb_.drain(nread);
+
+    auto htperr = HTTP_PARSER_ERRNO(proxy_htp_.get());
+
+    if (htperr != HPE_OK) {
+      return -1;
+    }
+
+    switch (state_) {
+    case Http2Session::PROXY_CONNECTED:
+      // Initiate SSL/TLS handshake through established tunnel.
+      if (initiate_connection() != 0) {
+        return -1;
+      }
+      return 0;
+    case Http2Session::PROXY_FAILED:
+      return -1;
+    }
+  }
+}
+
+int Http2Session::downstream_connect_proxy() {
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, this) << "Connected to the proxy";
+  }
+  std::string req = "CONNECT ";
+  req += get_config()->downstream_addrs[0].hostport.get();
+  req += " HTTP/1.1\r\nHost: ";
+  req += get_config()->downstream_addrs[0].host.get();
+  req += "\r\n";
+  if (get_config()->downstream_http_proxy_userinfo) {
+    req += "Proxy-Authorization: Basic ";
+    size_t len = strlen(get_config()->downstream_http_proxy_userinfo.get());
+    req += base64::encode(get_config()->downstream_http_proxy_userinfo.get(),
+                          get_config()->downstream_http_proxy_userinfo.get() +
+                              len);
+    req += "\r\n";
+  }
+  req += "\r\n";
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, this) << "HTTP proxy request headers\n" << req;
+  }
+  auto nwrite = wb_.write(req.c_str(), req.size());
+  if (nwrite != req.size()) {
+    SSLOG(WARN, this) << "HTTP proxy request is too large";
+    return -1;
+  }
+  on_write_ = &Http2Session::noop;
+
+  signal_write();
+  return 0;
+}
+
+void Http2Session::add_downstream_connection(Http2DownstreamConnection *dconn) {
+  dconns_.insert(dconn);
+}
+
+void
+Http2Session::remove_downstream_connection(Http2DownstreamConnection *dconn) {
+  dconns_.erase(dconn);
+  dconn->detach_stream_data();
+}
+
+void Http2Session::remove_stream_data(StreamData *sd) {
+  streams_.erase(sd);
+  if (sd->dconn) {
+    sd->dconn->detach_stream_data();
+  }
+  delete sd;
+}
+
+int Http2Session::submit_request(Http2DownstreamConnection *dconn, int32_t pri,
+                                 const nghttp2_nv *nva, size_t nvlen,
+                                 const nghttp2_data_provider *data_prd) {
+  assert(state_ == CONNECTED);
+  auto sd = make_unique<StreamData>();
+  // TODO Specify nullptr to pri_spec for now
+  auto stream_id =
+      nghttp2_submit_request(session_, nullptr, nva, nvlen, data_prd, sd.get());
+  if (stream_id < 0) {
+    SSLOG(FATAL, this) << "nghttp2_submit_request() failed: "
+                       << nghttp2_strerror(stream_id);
+    return -1;
+  }
+
+  dconn->attach_stream_data(sd.get());
+  dconn->get_downstream()->set_downstream_stream_id(stream_id);
+  streams_.insert(sd.release());
+
+  return 0;
+}
+
+int Http2Session::submit_rst_stream(int32_t stream_id, uint32_t error_code) {
+  assert(state_ == CONNECTED);
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, this) << "RST_STREAM stream_id=" << stream_id
+                      << " with error_code=" << error_code;
+  }
+  int rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, stream_id,
+                                     error_code);
+  if (rv != 0) {
+    SSLOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: "
+                       << nghttp2_strerror(rv);
+    return -1;
+  }
+  return 0;
+}
+
+int Http2Session::submit_priority(Http2DownstreamConnection *dconn,
+                                  int32_t pri) {
+  assert(state_ == CONNECTED);
+  if (!dconn) {
+    return 0;
+  }
+  int rv;
+
+  // TODO Disabled temporarily
+
+  // rv = nghttp2_submit_priority(session_, NGHTTP2_FLAG_NONE,
+  //                              dconn->get_downstream()->
+  //                              get_downstream_stream_id(), pri);
+
+  rv = 0;
+
+  if (rv < NGHTTP2_ERR_FATAL) {
+    SSLOG(FATAL, this) << "nghttp2_submit_priority() failed: "
+                       << nghttp2_strerror(rv);
+    return -1;
+  }
+  return 0;
+}
+
+nghttp2_session *Http2Session::get_session() const { return session_; }
+
+bool Http2Session::get_flow_control() const { return flow_control_; }
+
+int Http2Session::resume_data(Http2DownstreamConnection *dconn) {
+  assert(state_ == CONNECTED);
+  auto downstream = dconn->get_downstream();
+  int rv = nghttp2_session_resume_data(session_,
+                                       downstream->get_downstream_stream_id());
+  switch (rv) {
+  case 0:
+  case NGHTTP2_ERR_INVALID_ARGUMENT:
+    return 0;
+  default:
+    SSLOG(FATAL, this) << "nghttp2_resume_session() failed: "
+                       << nghttp2_strerror(rv);
+    return -1;
+  }
+}
+
+namespace {
+void call_downstream_readcb(Http2Session *http2session,
+                            Downstream *downstream) {
+  auto upstream = downstream->get_upstream();
+  if (!upstream) {
+    return;
+  }
+  if (upstream->downstream_read(downstream->get_downstream_connection()) != 0) {
+    delete upstream->get_client_handler();
+  }
+}
+} // namespace
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                             uint32_t error_code, void *user_data) {
+  auto http2session = static_cast<Http2Session *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, http2session) << "Stream stream_id=" << stream_id
+                              << " is being closed with error code "
+                              << error_code;
+  }
+  auto sd = static_cast<StreamData *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+  if (sd == 0) {
+    // We might get this close callback when pushed streams are
+    // closed.
+    return 0;
+  }
+  auto dconn = sd->dconn;
+  if (dconn) {
+    auto downstream = dconn->get_downstream();
+    if (downstream && downstream->get_downstream_stream_id() == stream_id) {
+
+      if (downstream->get_upgraded() &&
+          downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+        // For tunneled connection, we have to submit RST_STREAM to
+        // upstream *after* whole response body is sent. We just set
+        // MSG_COMPLETE here. Upstream will take care of that.
+        downstream->get_upstream()->on_downstream_body_complete(downstream);
+        downstream->set_response_state(Downstream::MSG_COMPLETE);
+      } else if (error_code == NGHTTP2_NO_ERROR) {
+        switch (downstream->get_response_state()) {
+        case Downstream::MSG_COMPLETE:
+        case Downstream::MSG_BAD_HEADER:
+          break;
+        default:
+          downstream->set_response_state(Downstream::MSG_RESET);
+        }
+      } else if (downstream->get_response_state() !=
+                 Downstream::MSG_BAD_HEADER) {
+        downstream->set_response_state(Downstream::MSG_RESET);
+      }
+      call_downstream_readcb(http2session, downstream);
+      // dconn may be deleted
+    }
+  }
+  // The life time of StreamData ends here
+  http2session->remove_stream_data(sd);
+  return 0;
+}
+} // namespace
+
+void Http2Session::start_settings_timer() {
+  ev_timer_again(conn_.loop, &settings_timer_);
+}
+
+void Http2Session::stop_settings_timer() {
+  ev_timer_stop(conn_.loop, &settings_timer_);
+}
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                       const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen, uint8_t flags,
+                       void *user_data) {
+  auto http2session = static_cast<Http2Session *>(user_data);
+  auto sd = static_cast<StreamData *>(
+      nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+  if (!sd || !sd->dconn) {
+    return 0;
+  }
+  auto downstream = sd->dconn->get_downstream();
+  if (!downstream) {
+    return 0;
+  }
+
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      (frame->headers.cat != NGHTTP2_HCAT_RESPONSE &&
+       !downstream->get_expect_final_response())) {
+    return 0;
+  }
+
+  if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
+    if (LOG_ENABLED(INFO)) {
+      DLOG(INFO, downstream) << "Too large header block size="
+                             << downstream->get_response_headers_sum();
+    }
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+  if (!http2::check_nv(name, namelen, value, valuelen)) {
+    return 0;
+  }
+
+  auto token = http2::lookup_token(name, namelen);
+
+  if (name[0] == ':') {
+    if (!downstream->response_pseudo_header_allowed(token)) {
+      http2session->submit_rst_stream(frame->hd.stream_id,
+                                      NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+  }
+
+  if (!http2::http2_header_allowed(token)) {
+    http2session->submit_rst_stream(frame->hd.stream_id,
+                                    NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  if (token == http2::HD_CONTENT_LENGTH) {
+    auto len = util::parse_uint(value, valuelen);
+    if (len == -1) {
+      http2session->submit_rst_stream(frame->hd.stream_id,
+                                      NGHTTP2_PROTOCOL_ERROR);
+      downstream->set_response_state(Downstream::MSG_BAD_HEADER);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    if (downstream->get_response_content_length() != -1) {
+      http2session->submit_rst_stream(frame->hd.stream_id,
+                                      NGHTTP2_PROTOCOL_ERROR);
+      downstream->set_response_state(Downstream::MSG_BAD_HEADER);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    downstream->set_response_content_length(len);
+  }
+
+  downstream->add_response_header(name, namelen, value, valuelen,
+                                  flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto http2session = static_cast<Http2Session *>(user_data);
+  if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
+    return 0;
+  }
+  auto sd = static_cast<StreamData *>(
+      nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+  if (!sd || !sd->dconn) {
+    http2session->submit_rst_stream(frame->hd.stream_id,
+                                    NGHTTP2_INTERNAL_ERROR);
+    return 0;
+  }
+  auto downstream = sd->dconn->get_downstream();
+  if (!downstream ||
+      downstream->get_downstream_stream_id() != frame->hd.stream_id) {
+    http2session->submit_rst_stream(frame->hd.stream_id,
+                                    NGHTTP2_INTERNAL_ERROR);
+    return 0;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_response_headers(Http2Session *http2session, Downstream *downstream,
+                        nghttp2_session *session, const nghttp2_frame *frame) {
+  int rv;
+
+  auto upstream = downstream->get_upstream();
+
+  auto &nva = downstream->get_response_headers();
+
+  downstream->set_expect_final_response(false);
+
+  auto status = downstream->get_response_header(http2::HD__STATUS);
+  int status_code;
+
+  if (!http2::non_empty_value(status) ||
+      (status_code = http2::parse_http_status_code(status->value)) == -1) {
+
+    http2session->submit_rst_stream(frame->hd.stream_id,
+                                    NGHTTP2_PROTOCOL_ERROR);
+    downstream->set_response_state(Downstream::MSG_RESET);
+    call_downstream_readcb(http2session, downstream);
+
+    return 0;
+  }
+
+  downstream->set_response_http_status(status_code);
+  downstream->set_response_major(2);
+  downstream->set_response_minor(0);
+
+  if (LOG_ENABLED(INFO)) {
+    std::stringstream ss;
+    for (auto &nv : nva) {
+      ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+    }
+    SSLOG(INFO, http2session)
+        << "HTTP response headers. stream_id=" << frame->hd.stream_id << "\n"
+        << ss.str();
+  }
+
+  if (downstream->get_non_final_response()) {
+
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, http2session) << "This is non-final response.";
+    }
+
+    downstream->set_expect_final_response(true);
+    rv = upstream->on_downstream_header_complete(downstream);
+
+    // Now Dowstream's response headers are erased.
+
+    if (rv != 0) {
+      http2session->submit_rst_stream(frame->hd.stream_id,
+                                      NGHTTP2_PROTOCOL_ERROR);
+      downstream->set_response_state(Downstream::MSG_RESET);
+    }
+
+    return 0;
+  }
+
+  if (downstream->get_response_content_length() == -1 &&
+      downstream->expect_response_body()) {
+    // Here we have response body but Content-Length is not known in
+    // advance.
+    if (downstream->get_request_major() <= 0 ||
+        (downstream->get_request_major() <= 1 &&
+         downstream->get_request_minor() <= 0)) {
+      // We simply close connection for pre-HTTP/1.1 in this case.
+      downstream->set_response_connection_close(true);
+    } else if (downstream->get_request_method() != "CONNECT") {
+      // Otherwise, use chunked encoding to keep upstream connection
+      // open.  In HTTP2, we are supporsed not to receive
+      // transfer-encoding.
+      downstream->add_response_header("transfer-encoding", "chunked",
+                                      http2::HD_TRANSFER_ENCODING);
+      downstream->set_chunked_response(true);
+    }
+  }
+
+  downstream->set_response_state(Downstream::HEADER_COMPLETE);
+  downstream->check_upgrade_fulfilled();
+  if (downstream->get_upgraded()) {
+    downstream->set_response_connection_close(true);
+    // On upgrade sucess, both ends can send data
+    if (upstream->resume_read(SHRPX_MSG_BLOCK, downstream, 0) != 0) {
+      // If resume_read fails, just drop connection. Not ideal.
+      delete upstream->get_client_handler();
+      return -1;
+    }
+    downstream->set_request_state(Downstream::HEADER_COMPLETE);
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, http2session)
+          << "HTTP upgrade success. stream_id=" << frame->hd.stream_id;
+    }
+  } else if (downstream->get_request_method() == "CONNECT") {
+    // If request is CONNECT, terminate request body to avoid for
+    // stream to stall.
+    downstream->end_upload_data();
+  }
+  rv = upstream->on_downstream_header_complete(downstream);
+  if (rv != 0) {
+    http2session->submit_rst_stream(frame->hd.stream_id,
+                                    NGHTTP2_PROTOCOL_ERROR);
+    downstream->set_response_state(Downstream::MSG_RESET);
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  int rv;
+  auto http2session = static_cast<Http2Session *>(user_data);
+
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA: {
+    auto sd = static_cast<StreamData *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+    if (!sd || !sd->dconn) {
+      break;
+    }
+    auto downstream = sd->dconn->get_downstream();
+    if (!downstream ||
+        downstream->get_downstream_stream_id() != frame->hd.stream_id) {
+      break;
+    }
+
+    auto upstream = downstream->get_upstream();
+    rv = upstream->on_downstream_body(downstream, nullptr, 0, true);
+    if (rv != 0) {
+      http2session->submit_rst_stream(frame->hd.stream_id,
+                                      NGHTTP2_INTERNAL_ERROR);
+      downstream->set_response_state(Downstream::MSG_RESET);
+
+    } else if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+
+      downstream->disable_downstream_rtimer();
+
+      if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+
+        downstream->set_response_state(Downstream::MSG_COMPLETE);
+
+        rv = upstream->on_downstream_body_complete(downstream);
+
+        if (rv != 0) {
+          downstream->set_response_state(Downstream::MSG_RESET);
+        }
+      }
+    }
+
+    call_downstream_readcb(http2session, downstream);
+    break;
+  }
+  case NGHTTP2_HEADERS: {
+    auto sd = static_cast<StreamData *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+    if (!sd || !sd->dconn) {
+      break;
+    }
+    auto downstream = sd->dconn->get_downstream();
+
+    if (!downstream) {
+      return 0;
+    }
+
+    if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE) {
+      rv = on_response_headers(http2session, downstream, session, frame);
+
+      if (rv != 0) {
+        return 0;
+      }
+    } else if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
+      if (downstream->get_expect_final_response()) {
+        rv = on_response_headers(http2session, downstream, session, frame);
+
+        if (rv != 0) {
+          return 0;
+        }
+      } else if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+        http2session->submit_rst_stream(frame->hd.stream_id,
+                                        NGHTTP2_PROTOCOL_ERROR);
+        return 0;
+      }
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+
+      downstream->disable_downstream_rtimer();
+
+      if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+        downstream->set_response_state(Downstream::MSG_COMPLETE);
+
+        auto upstream = downstream->get_upstream();
+
+        rv = upstream->on_downstream_body_complete(downstream);
+
+        if (rv != 0) {
+          downstream->set_response_state(Downstream::MSG_RESET);
+        }
+      }
+    } else {
+      downstream->reset_downstream_rtimer();
+    }
+
+    // This may delete downstream
+    call_downstream_readcb(http2session, downstream);
+
+    break;
+  }
+  case NGHTTP2_RST_STREAM: {
+    auto sd = static_cast<StreamData *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+    if (sd && sd->dconn) {
+      auto downstream = sd->dconn->get_downstream();
+      if (downstream &&
+          downstream->get_downstream_stream_id() == frame->hd.stream_id) {
+
+        downstream->set_response_rst_stream_error_code(
+            frame->rst_stream.error_code);
+        call_downstream_readcb(http2session, downstream);
+      }
+    }
+    break;
+  }
+  case NGHTTP2_SETTINGS:
+    if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+      break;
+    }
+    http2session->stop_settings_timer();
+    break;
+  case NGHTTP2_PUSH_PROMISE:
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, http2session)
+          << "Received downstream PUSH_PROMISE stream_id="
+          << frame->hd.stream_id
+          << ", promised_stream_id=" << frame->push_promise.promised_stream_id;
+    }
+    // We just respond with RST_STREAM.
+    http2session->submit_rst_stream(frame->push_promise.promised_stream_id,
+                                    NGHTTP2_REFUSED_STREAM);
+    break;
+  default:
+    break;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) {
+  int rv;
+  auto http2session = static_cast<Http2Session *>(user_data);
+  auto sd = static_cast<StreamData *>(
+      nghttp2_session_get_stream_user_data(session, stream_id));
+  if (!sd || !sd->dconn) {
+    http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
+
+    if (http2session->consume(stream_id, len) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+  auto downstream = sd->dconn->get_downstream();
+  if (!downstream || downstream->get_downstream_stream_id() != stream_id ||
+      !downstream->expect_response_body()) {
+
+    http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
+
+    if (http2session->consume(stream_id, len) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+
+  // We don't want DATA after non-final response, which is illegal in
+  // HTTP.
+  if (downstream->get_non_final_response()) {
+    http2session->submit_rst_stream(stream_id, NGHTTP2_PROTOCOL_ERROR);
+
+    if (http2session->consume(stream_id, len) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+
+  downstream->reset_downstream_rtimer();
+
+  downstream->add_response_bodylen(len);
+
+  auto upstream = downstream->get_upstream();
+  rv = upstream->on_downstream_body(downstream, data, len, false);
+  if (rv != 0) {
+    http2session->submit_rst_stream(stream_id, NGHTTP2_INTERNAL_ERROR);
+
+    if (http2session->consume(stream_id, len) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    downstream->set_response_state(Downstream::MSG_RESET);
+  }
+
+  downstream->add_response_datalen(len);
+
+  call_downstream_readcb(http2session, downstream);
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  auto http2session = static_cast<Http2Session *>(user_data);
+
+  if (frame->hd.type == NGHTTP2_DATA || frame->hd.type == NGHTTP2_HEADERS) {
+    if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
+      return 0;
+    }
+
+    auto sd = static_cast<StreamData *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+
+    if (!sd || !sd->dconn) {
+      return 0;
+    }
+
+    auto downstream = sd->dconn->get_downstream();
+
+    if (!downstream ||
+        downstream->get_downstream_stream_id() != frame->hd.stream_id) {
+      return 0;
+    }
+
+    downstream->reset_downstream_rtimer();
+
+    return 0;
+  }
+
+  if (frame->hd.type == NGHTTP2_SETTINGS &&
+      (frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+    http2session->start_settings_timer();
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_not_send_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, int lib_error_code,
+                               void *user_data) {
+  auto http2session = static_cast<Http2Session *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, http2session) << "Failed to send control frame type="
+                              << static_cast<uint32_t>(frame->hd.type)
+                              << "lib_error_code=" << lib_error_code << ":"
+                              << nghttp2_strerror(lib_error_code);
+  }
+  if (frame->hd.type == NGHTTP2_HEADERS &&
+      frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+    // To avoid stream hanging around, flag Downstream::MSG_RESET and
+    // terminate the upstream and downstream connections.
+    auto sd = static_cast<StreamData *>(
+        nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
+    if (!sd) {
+      return 0;
+    }
+    if (sd->dconn) {
+      auto downstream = sd->dconn->get_downstream();
+      if (!downstream ||
+          downstream->get_downstream_stream_id() != frame->hd.stream_id) {
+        return 0;
+      }
+      downstream->set_response_state(Downstream::MSG_RESET);
+      call_downstream_readcb(http2session, downstream);
+    }
+    http2session->remove_stream_data(sd);
+  }
+  return 0;
+}
+} // namespace
+
+int Http2Session::on_connect() {
+  int rv;
+
+  state_ = Http2Session::CONNECTED;
+
+  if (ssl_ctx_) {
+    const unsigned char *next_proto = nullptr;
+    unsigned int next_proto_len;
+    SSL_get0_next_proto_negotiated(conn_.tls.ssl, &next_proto, &next_proto_len);
+    for (int i = 0; i < 2; ++i) {
+      if (next_proto) {
+        if (LOG_ENABLED(INFO)) {
+          std::string proto(next_proto, next_proto + next_proto_len);
+          SSLOG(INFO, this) << "Negotiated next protocol: " << proto;
+        }
+        if (!util::check_h2_is_selected(next_proto, next_proto_len)) {
+          return -1;
+        }
+        break;
+      }
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+      SSL_get0_alpn_selected(conn_.tls.ssl, &next_proto, &next_proto_len);
+#else  // OPENSSL_VERSION_NUMBER < 0x10002000L
+      break;
+#endif // OPENSSL_VERSION_NUMBER < 0x10002000L
+    }
+    if (!next_proto) {
+      return -1;
+    }
+  }
+
+  nghttp2_session_callbacks *callbacks;
+  rv = nghttp2_session_callbacks_new(&callbacks);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  auto callbacks_deleter = defer(nghttp2_session_callbacks_del, callbacks);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+
+  nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+                                                       on_frame_send_callback);
+
+  nghttp2_session_callbacks_set_on_frame_not_send_callback(
+      callbacks, on_frame_not_send_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+
+  if (get_config()->padding) {
+    nghttp2_session_callbacks_set_select_padding_callback(
+        callbacks, http::select_padding_callback);
+  }
+
+  rv = nghttp2_session_client_new2(&session_, callbacks, this,
+                                   get_config()->http2_client_option);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  flow_control_ = true;
+
+  std::array<nghttp2_settings_entry, 3> entry;
+  entry[0].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  entry[0].value = 0;
+  entry[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  entry[1].value = get_config()->http2_max_concurrent_streams;
+
+  entry[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  entry[2].value = (1 << get_config()->http2_downstream_window_bits) - 1;
+
+  rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
+                               entry.size());
+  if (rv != 0) {
+    return -1;
+  }
+
+  if (get_config()->http2_downstream_connection_window_bits > 16) {
+    int32_t delta =
+        (1 << get_config()->http2_downstream_connection_window_bits) - 1 -
+        NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
+    rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0, delta);
+    if (rv != 0) {
+      return -1;
+    }
+  }
+
+  auto nwrite = wb_.write(NGHTTP2_CLIENT_CONNECTION_PREFACE,
+                          NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+  if (nwrite != NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN) {
+    SSLOG(FATAL, this) << "buffer is too small to send connection preface";
+    return -1;
+  }
+
+  auto must_terminate = !get_config()->downstream_no_tls &&
+                        !ssl::check_http2_requirement(conn_.tls.ssl);
+
+  if (must_terminate) {
+    rv = terminate_session(NGHTTP2_INADEQUATE_SECURITY);
+
+    if (rv != 0) {
+      return -1;
+    }
+  }
+
+  if (must_terminate) {
+    return 0;
+  }
+
+  reset_connection_check_timer();
+
+  // submit pending request
+  for (auto dconn : dconns_) {
+    if (dconn->push_request_headers() == 0) {
+      auto downstream = dconn->get_downstream();
+      auto upstream = downstream->get_upstream();
+      upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
+      continue;
+    }
+
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, this) << "backend request failed";
+    }
+
+    auto downstream = dconn->get_downstream();
+
+    if (!downstream) {
+      continue;
+    }
+
+    auto upstream = downstream->get_upstream();
+
+    upstream->on_downstream_abort_request(downstream, 400);
+  }
+  signal_write();
+  return 0;
+}
+
+int Http2Session::do_read() { return read_(*this); }
+int Http2Session::do_write() { return write_(*this); }
+
+int Http2Session::on_read() { return on_read_(*this); }
+int Http2Session::on_write() { return on_write_(*this); }
+
+int Http2Session::downstream_read() {
+  ssize_t rv = 0;
+
+  if (rb_.rleft() > 0) {
+    rv = nghttp2_session_mem_recv(
+        session_, reinterpret_cast<const uint8_t *>(rb_.pos), rb_.rleft());
+
+    if (rv < 0) {
+      SSLOG(ERROR, this) << "nghttp2_session_recv() returned error: "
+                         << nghttp2_strerror(rv);
+      return -1;
+    }
+
+    // nghttp2_session_mem_recv() should consume all input data in
+    // case of success.
+    rb_.reset();
+  }
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, this) << "No more read/write for this HTTP2 session";
+    }
+    return -1;
+  }
+
+  signal_write();
+  return 0;
+}
+
+int Http2Session::downstream_write() {
+  if (data_pending_) {
+    auto n = std::min(wb_.wleft(), data_pendinglen_);
+    wb_.write(data_pending_, n);
+    if (n < data_pendinglen_) {
+      data_pending_ += n;
+      data_pendinglen_ -= n;
+      return 0;
+    }
+
+    data_pending_ = nullptr;
+    data_pendinglen_ = 0;
+  }
+
+  for (;;) {
+    const uint8_t *data;
+    auto datalen = nghttp2_session_mem_send(session_, &data);
+
+    if (datalen < 0) {
+      SSLOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
+                         << nghttp2_strerror(datalen);
+      return -1;
+    }
+    if (datalen == 0) {
+      break;
+    }
+    auto n = wb_.write(data, datalen);
+    if (n < static_cast<decltype(n)>(datalen)) {
+      data_pending_ = data + n;
+      data_pendinglen_ = datalen - n;
+      return 0;
+    }
+  }
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && wb_.rleft() == 0) {
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, this) << "No more read/write for this session";
+    }
+    return -1;
+  }
+
+  return 0;
+}
+
+void Http2Session::signal_write() { write_requested_ = true; }
+
+void Http2Session::clear_write_request() { write_requested_ = false; }
+
+bool Http2Session::write_requested() const { return write_requested_; }
+
+struct ev_loop *Http2Session::get_loop() const {
+  return conn_.loop;
+}
+
+ev_io *Http2Session::get_wev() { return &conn_.wev; }
+
+int Http2Session::get_state() const { return state_; }
+
+void Http2Session::set_state(int state) { state_ = state; }
+
+int Http2Session::terminate_session(uint32_t error_code) {
+  int rv;
+  rv = nghttp2_session_terminate_session(session_, error_code);
+  if (rv != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+SSL *Http2Session::get_ssl() const { return conn_.tls.ssl; }
+
+int Http2Session::consume(int32_t stream_id, size_t len) {
+  int rv;
+
+  if (!session_) {
+    return 0;
+  }
+
+  rv = nghttp2_session_consume(session_, stream_id, len);
+
+  if (rv != 0) {
+    SSLOG(WARN, this) << "nghttp2_session_consume() returned error: "
+                      << nghttp2_strerror(rv);
+
+    return -1;
+  }
+
+  return 0;
+}
+
+bool Http2Session::can_push_request() const {
+  return state_ == CONNECTED &&
+         connection_check_state_ == CONNECTION_CHECK_NONE;
+}
+
+void Http2Session::start_checking_connection() {
+  if (state_ != CONNECTED ||
+      connection_check_state_ != CONNECTION_CHECK_REQUIRED) {
+    return;
+  }
+  connection_check_state_ = CONNECTION_CHECK_STARTED;
+
+  SSLOG(INFO, this) << "Start checking connection";
+  // If connection is down, we may get error when writing data.  Issue
+  // ping frame to see whether connection is alive.
+  nghttp2_submit_ping(session_, NGHTTP2_FLAG_NONE, NULL);
+
+  signal_write();
+}
+
+void Http2Session::reset_connection_check_timer() {
+  ev_timer_again(conn_.loop, &connchk_timer_);
+}
+
+void Http2Session::connection_alive() {
+  reset_connection_check_timer();
+
+  if (connection_check_state_ == CONNECTION_CHECK_NONE) {
+    return;
+  }
+
+  SSLOG(INFO, this) << "Connection alive";
+  connection_check_state_ = CONNECTION_CHECK_NONE;
+
+  // submit pending request
+  for (auto dconn : dconns_) {
+    auto downstream = dconn->get_downstream();
+    if (!downstream ||
+        (downstream->get_request_state() != Downstream::HEADER_COMPLETE &&
+         downstream->get_request_state() != Downstream::MSG_COMPLETE) ||
+        downstream->get_response_state() != Downstream::INITIAL) {
+      continue;
+    }
+
+    auto upstream = downstream->get_upstream();
+
+    if (dconn->push_request_headers() == 0) {
+      upstream->resume_read(SHRPX_NO_BUFFER, downstream, 0);
+      continue;
+    }
+
+    if (LOG_ENABLED(INFO)) {
+      SSLOG(INFO, this) << "backend request failed";
+    }
+
+    upstream->on_downstream_abort_request(downstream, 400);
+  }
+}
+
+void Http2Session::set_connection_check_state(int state) {
+  connection_check_state_ = state;
+}
+
+int Http2Session::noop() { return 0; }
+
+int Http2Session::connected() {
+  if (!util::check_socket_connected(conn_.fd)) {
+    return -1;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, this) << "Connection established";
+  }
+
+  conn_.rlimit.startw();
+
+  if (conn_.tls.ssl) {
+    read_ = &Http2Session::tls_handshake;
+    write_ = &Http2Session::tls_handshake;
+
+    return do_write();
+  }
+
+  read_ = &Http2Session::read_clear;
+  write_ = &Http2Session::write_clear;
+
+  if (state_ == PROXY_CONNECTING) {
+    return do_write();
+  }
+
+  if (on_connect() != 0) {
+    state_ = CONNECT_FAILING;
+    return -1;
+  }
+
+  return 0;
+}
+
+int Http2Session::read_clear() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  for (;;) {
+    // we should process buffered data first before we read EOF.
+    if (rb_.rleft() && on_read() != 0) {
+      return -1;
+    }
+    if (rb_.rleft()) {
+      return 0;
+    }
+    rb_.reset();
+
+    auto nread = conn_.read_clear(rb_.last, rb_.wleft());
+
+    if (nread == 0) {
+      return 0;
+    }
+
+    if (nread < 0) {
+      return nread;
+    }
+
+    rb_.write(nread);
+  }
+}
+
+int Http2Session::write_clear() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  for (;;) {
+    if (wb_.rleft() > 0) {
+      auto nwrite = conn_.write_clear(wb_.pos, wb_.rleft());
+
+      if (nwrite == 0) {
+        return 0;
+      }
+
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      wb_.drain(nwrite);
+      continue;
+    }
+
+    wb_.reset();
+    if (on_write() != 0) {
+      return -1;
+    }
+    if (wb_.rleft() == 0) {
+      break;
+    }
+  }
+
+  conn_.wlimit.stopw();
+  ev_timer_stop(conn_.loop, &conn_.wt);
+
+  return 0;
+}
+
+int Http2Session::tls_handshake() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  ERR_clear_error();
+
+  auto rv = conn_.tls_handshake();
+
+  if (rv == SHRPX_ERR_INPROGRESS) {
+    return 0;
+  }
+
+  if (rv < 0) {
+    return rv;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    SSLOG(INFO, this) << "SSL/TLS handshake completed";
+  }
+
+  if (!get_config()->downstream_no_tls && !get_config()->insecure &&
+      check_cert() != 0) {
+    return -1;
+  }
+
+  read_ = &Http2Session::read_tls;
+  write_ = &Http2Session::write_tls;
+
+  if (on_connect() != 0) {
+    state_ = CONNECT_FAILING;
+    return -1;
+  }
+
+  return 0;
+}
+
+int Http2Session::read_tls() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  ERR_clear_error();
+
+  for (;;) {
+    // we should process buffered data first before we read EOF.
+    if (rb_.rleft() && on_read() != 0) {
+      return -1;
+    }
+    if (rb_.rleft()) {
+      return 0;
+    }
+    rb_.reset();
+
+    auto nread = conn_.read_tls(rb_.last, rb_.wleft());
+
+    if (nread == 0) {
+      return 0;
+    }
+
+    if (nread < 0) {
+      return nread;
+    }
+
+    rb_.write(nread);
+  }
+}
+
+int Http2Session::write_tls() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  ERR_clear_error();
+
+  for (;;) {
+    if (wb_.rleft() > 0) {
+      auto nwrite = conn_.write_tls(wb_.pos, wb_.rleft());
+
+      if (nwrite == 0) {
+        return 0;
+      }
+
+      if (nwrite < 0) {
+        return nwrite;
+      }
+
+      wb_.drain(nwrite);
+
+      continue;
+    }
+    wb_.reset();
+    if (on_write() != 0) {
+      return -1;
+    }
+    if (wb_.rleft() == 0) {
+      break;
+    }
+  }
+
+  conn_.wlimit.stopw();
+  ev_timer_stop(conn_.loop, &conn_.wt);
+
+  return 0;
+}
+
+bool Http2Session::should_hard_fail() const {
+  switch (state_) {
+  case PROXY_CONNECTING:
+  case PROXY_FAILED:
+  case CONNECTING:
+  case CONNECT_FAILING:
+    return true;
+  default:
+    return false;
+  }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_session.h b/src/shrpx_http2_session.h
new file mode 100644 (file)
index 0000000..1fc4852
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_SESSION_H
+#define SHRPX_HTTP2_SESSION_H
+
+#include "shrpx.h"
+
+#include <set>
+#include <memory>
+
+#include <openssl/ssl.h>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "http-parser/http_parser.h"
+
+#include "shrpx_connection.h"
+#include "buffer.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class Http2DownstreamConnection;
+
+struct StreamData {
+  Http2DownstreamConnection *dconn;
+};
+
+class Http2Session {
+public:
+  Http2Session(struct ev_loop *loop, SSL_CTX *ssl_ctx);
+  ~Http2Session();
+
+  int check_cert();
+
+  // If hard is true, all pending requests are abandoned and
+  // associated ClientHandlers will be deleted.
+  int disconnect(bool hard = false);
+  int initiate_connection();
+
+  void add_downstream_connection(Http2DownstreamConnection *dconn);
+  void remove_downstream_connection(Http2DownstreamConnection *dconn);
+
+  void remove_stream_data(StreamData *sd);
+
+  int submit_request(Http2DownstreamConnection *dconn, int32_t pri,
+                     const nghttp2_nv *nva, size_t nvlen,
+                     const nghttp2_data_provider *data_prd);
+
+  int submit_rst_stream(int32_t stream_id, uint32_t error_code);
+
+  int submit_priority(Http2DownstreamConnection *dconn, int32_t pri);
+
+  int terminate_session(uint32_t error_code);
+
+  nghttp2_session *get_session() const;
+
+  bool get_flow_control() const;
+
+  int resume_data(Http2DownstreamConnection *dconn);
+
+  int on_connect();
+
+  int do_read();
+  int do_write();
+
+  int on_read();
+  int on_write();
+
+  int connected();
+  int read_clear();
+  int write_clear();
+  int tls_handshake();
+  int read_tls();
+  int write_tls();
+
+  int downstream_read_proxy();
+  int downstream_connect_proxy();
+
+  int downstream_read();
+  int downstream_write();
+
+  int noop();
+
+  void signal_write();
+  void clear_write_request();
+  bool write_requested() const;
+
+  struct ev_loop *get_loop() const;
+
+  ev_io *get_wev();
+
+  int get_state() const;
+  void set_state(int state);
+
+  void start_settings_timer();
+  void stop_settings_timer();
+
+  SSL *get_ssl() const;
+
+  int consume(int32_t stream_id, size_t len);
+
+  // Returns true if request can be issued on downstream connection.
+  bool can_push_request() const;
+  // Initiates the connection checking if downstream connection has
+  // been established and connection checking is required.
+  void start_checking_connection();
+  // Resets connection check timer.  After timeout, we require
+  // connection checking.
+  void reset_connection_check_timer();
+  // Signals that connection is alive.  Internally
+  // reset_connection_check_timer() is called.
+  void connection_alive();
+  // Change connection check state.
+  void set_connection_check_state(int state);
+
+  bool should_hard_fail() const;
+
+  enum {
+    // Disconnected
+    DISCONNECTED,
+    // Connecting proxy and making CONNECT request
+    PROXY_CONNECTING,
+    // Tunnel is established with proxy
+    PROXY_CONNECTED,
+    // Establishing tunnel is failed
+    PROXY_FAILED,
+    // Connecting to downstream and/or performing SSL/TLS handshake
+    CONNECTING,
+    // Connected to downstream
+    CONNECTED,
+    // Connection is started to fail
+    CONNECT_FAILING,
+  };
+
+  static const size_t OUTBUF_MAX_THRES = 64 * 1024;
+
+  enum {
+    // Connection checking is not required
+    CONNECTION_CHECK_NONE,
+    // Connection checking is required
+    CONNECTION_CHECK_REQUIRED,
+    // Connection checking has been started
+    CONNECTION_CHECK_STARTED
+  };
+
+  using ReadBuf = Buffer<8192>;
+  using WriteBuf = Buffer<32768>;
+
+private:
+  Connection conn_;
+  ev_timer settings_timer_;
+  ev_timer connchk_timer_;
+  ev_prepare wrsched_prep_;
+  std::set<Http2DownstreamConnection *> dconns_;
+  std::set<StreamData *> streams_;
+  std::function<int(Http2Session &)> read_, write_;
+  std::function<int(Http2Session &)> on_read_, on_write_;
+  // Used to parse the response from HTTP proxy
+  std::unique_ptr<http_parser> proxy_htp_;
+  // NULL if no TLS is configured
+  SSL_CTX *ssl_ctx_;
+  nghttp2_session *session_;
+  const uint8_t *data_pending_;
+  size_t data_pendinglen_;
+  int state_;
+  int connection_check_state_;
+  bool flow_control_;
+  bool write_requested_;
+  WriteBuf wb_;
+  ReadBuf rb_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_SESSION_H
diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc
new file mode 100644 (file)
index 0000000..2874a45
--- /dev/null
@@ -0,0 +1,1650 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http2_upstream.h"
+
+#include <netinet/tcp.h>
+#include <assert.h>
+#include <cerrno>
+#include <sstream>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_https_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_config.h"
+#include "shrpx_http.h"
+#include "shrpx_worker_config.h"
+#include "http2.h"
+#include "util.h"
+#include "base64.h"
+#include "app_helper.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
+                             uint32_t error_code, void *user_data) {
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Stream stream_id=" << stream_id
+                         << " is being closed";
+  }
+
+  auto downstream = upstream->find_downstream(stream_id);
+
+  if (!downstream) {
+    return 0;
+  }
+
+  upstream->consume(stream_id, downstream->get_request_datalen());
+
+  downstream->reset_request_datalen();
+
+  if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
+    upstream->remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  downstream->set_request_state(Downstream::STREAM_CLOSED);
+
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    // At this point, downstream response was read
+    if (!downstream->get_upgraded() &&
+        !downstream->get_response_connection_close()) {
+      // Keep-alive
+      downstream->detach_downstream_connection();
+    }
+
+    upstream->remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  // At this point, downstream read may be paused.
+
+  // If shrpx_downstream::push_request_headers() failed, the
+  // error is handled here.
+  upstream->remove_downstream(downstream);
+  // downstream was deleted
+
+  // How to test this case? Request sufficient large download
+  // and make client send RST_STREAM after it gets first DATA
+  // frame chunk.
+
+  return 0;
+}
+} // namespace
+
+int Http2Upstream::upgrade_upstream(HttpsUpstream *http) {
+  int rv;
+
+  auto http2_settings = http->get_downstream()->get_http2_settings();
+  util::to_base64(http2_settings);
+
+  auto settings_payload =
+      base64::decode(std::begin(http2_settings), std::end(http2_settings));
+
+  rv = nghttp2_session_upgrade(
+      session_, reinterpret_cast<const uint8_t *>(settings_payload.c_str()),
+      settings_payload.size(), nullptr);
+  if (rv != 0) {
+    ULOG(WARN, this) << "nghttp2_session_upgrade() returned error: "
+                     << nghttp2_strerror(rv);
+    return -1;
+  }
+  pre_upstream_.reset(http);
+  auto downstream = http->pop_downstream();
+  downstream->reset_upstream(this);
+  downstream->set_stream_id(1);
+  downstream->reset_upstream_rtimer();
+  downstream->set_stream_id(1);
+  downstream->set_priority(0);
+
+  downstream_queue_.add_active(std::move(downstream));
+
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, this) << "Connection upgraded to HTTP/2";
+  }
+
+  return 0;
+}
+
+void Http2Upstream::start_settings_timer() {
+  ev_timer_start(handler_->get_loop(), &settings_timer_);
+}
+
+void Http2Upstream::stop_settings_timer() {
+  ev_timer_stop(handler_->get_loop(), &settings_timer_);
+}
+
+namespace {
+int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                       const uint8_t *name, size_t namelen,
+                       const uint8_t *value, size_t valuelen, uint8_t flags,
+                       void *user_data) {
+  if (get_config()->upstream_frame_debug) {
+    verbose_on_header_callback(session, frame, name, namelen, value, valuelen,
+                               flags, user_data);
+  }
+  if (frame->hd.type != NGHTTP2_HEADERS ||
+      frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+  auto downstream = upstream->find_downstream(frame->hd.stream_id);
+  if (!downstream) {
+    return 0;
+  }
+
+  if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
+    if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+      return 0;
+    }
+
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, upstream) << "Too large header block size="
+                           << downstream->get_request_headers_sum();
+    }
+
+    if (upstream->error_reply(downstream, 431) != 0) {
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+  if (!http2::check_nv(name, namelen, value, valuelen)) {
+    upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  auto token = http2::lookup_token(name, namelen);
+
+  if (name[0] == ':') {
+    if (!downstream->request_pseudo_header_allowed(token)) {
+      upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+  }
+
+  if (!http2::http2_header_allowed(token)) {
+    upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+    return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+  }
+
+  switch (token) {
+  case http2::HD_CONTENT_LENGTH: {
+    auto len = util::parse_uint(value, valuelen);
+    if (len == -1) {
+      if (upstream->error_reply(downstream, 400) != 0) {
+        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+      }
+      return 0;
+    }
+    if (downstream->get_request_content_length() != -1) {
+      if (upstream->error_reply(downstream, 400) != 0) {
+        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+      }
+      return 0;
+    }
+    downstream->set_request_content_length(len);
+    break;
+  }
+  case http2::HD_TE:
+    if (!util::strieq("trailers", value, valuelen)) {
+      upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+      return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+    }
+    break;
+  }
+
+  downstream->add_request_header(name, namelen, value, valuelen,
+                                 flags & NGHTTP2_NV_FLAG_NO_INDEX, token);
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_begin_headers_callback(nghttp2_session *session,
+                              const nghttp2_frame *frame, void *user_data) {
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+
+  if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+    return 0;
+  }
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Received upstream request HEADERS stream_id="
+                         << frame->hd.stream_id;
+  }
+
+  // TODO Use priority 0 for now
+  auto downstream = make_unique<Downstream>(upstream, frame->hd.stream_id, 0);
+
+  downstream->reset_upstream_rtimer();
+
+  // Although, we deprecated minor version from HTTP/2, we supply
+  // minor version 0 to use via header field in a conventional way.
+  downstream->set_request_major(2);
+  downstream->set_request_minor(0);
+
+  upstream->add_pending_downstream(std::move(downstream));
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
+                       nghttp2_session *session, const nghttp2_frame *frame) {
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    return 0;
+  }
+
+  auto &nva = downstream->get_request_headers();
+
+  if (LOG_ENABLED(INFO)) {
+    std::stringstream ss;
+    for (auto &nv : nva) {
+      ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+    }
+    ULOG(INFO, upstream) << "HTTP request headers. stream_id="
+                         << downstream->get_stream_id() << "\n" << ss.str();
+  }
+
+  if (get_config()->http2_upstream_dump_request_header) {
+    http2::dump_nv(get_config()->http2_upstream_dump_request_header, nva);
+  }
+
+  auto host = downstream->get_request_header(http2::HD_HOST);
+  auto authority = downstream->get_request_header(http2::HD__AUTHORITY);
+  auto path = downstream->get_request_header(http2::HD__PATH);
+  auto method = downstream->get_request_header(http2::HD__METHOD);
+  auto scheme = downstream->get_request_header(http2::HD__SCHEME);
+
+  bool is_connect = method && "CONNECT" == method->value;
+  bool having_host = http2::non_empty_value(host);
+  bool having_authority = http2::non_empty_value(authority);
+
+  if (is_connect) {
+    // Here we strictly require :authority header field.
+    if (scheme || path || !having_authority) {
+
+      upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+
+      return 0;
+    }
+  } else {
+    // For proxy, :authority is required. Otherwise, we can accept
+    // :authority or host for methods.
+    if (!http2::non_empty_value(method) || !http2::non_empty_value(scheme) ||
+        (get_config()->http2_proxy && !having_authority) ||
+        (!get_config()->http2_proxy && !having_authority && !having_host) ||
+        !http2::non_empty_value(path)) {
+
+      upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+
+      return 0;
+    }
+  }
+
+  downstream->set_request_method(http2::value_to_str(method));
+  downstream->set_request_http2_scheme(http2::value_to_str(scheme));
+  downstream->set_request_http2_authority(http2::value_to_str(authority));
+  downstream->set_request_path(http2::value_to_str(path));
+
+  if (!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
+    downstream->set_request_http2_expect_body(true);
+  }
+
+  downstream->inspect_http2_request();
+
+  downstream->set_request_state(Downstream::HEADER_COMPLETE);
+  if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+    if (!downstream->validate_request_bodylen()) {
+      upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+      return 0;
+    }
+
+    downstream->disable_upstream_rtimer();
+
+    downstream->set_request_state(Downstream::MSG_COMPLETE);
+  }
+
+  upstream->start_downstream(downstream);
+
+  return 0;
+}
+} // namespace
+
+void Http2Upstream::start_downstream(Downstream *downstream) {
+  auto next_downstream =
+      downstream_queue_.pop_pending(downstream->get_stream_id());
+  assert(next_downstream);
+
+  if (downstream_queue_.can_activate(
+          downstream->get_request_http2_authority())) {
+    initiate_downstream(std::move(next_downstream));
+    return;
+  }
+
+  downstream_queue_.add_blocked(std::move(next_downstream));
+}
+
+void
+Http2Upstream::initiate_downstream(std::unique_ptr<Downstream> downstream) {
+  int rv;
+
+  rv = downstream->attach_downstream_connection(
+      handler_->get_downstream_connection());
+  if (rv != 0) {
+    // downstream connection fails, send error page
+    if (error_reply(downstream.get(), 503) != 0) {
+      rst_stream(downstream.get(), NGHTTP2_INTERNAL_ERROR);
+    }
+
+    downstream->set_request_state(Downstream::CONNECT_FAIL);
+
+    downstream_queue_.add_failure(std::move(downstream));
+
+    return;
+  }
+  rv = downstream->push_request_headers();
+  if (rv != 0) {
+
+    if (error_reply(downstream.get(), 503) != 0) {
+      rst_stream(downstream.get(), NGHTTP2_INTERNAL_ERROR);
+    }
+
+    downstream_queue_.add_failure(std::move(downstream));
+
+    return;
+  }
+
+  downstream_queue_.add_active(std::move(downstream));
+
+  return;
+}
+
+namespace {
+int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  int rv;
+  if (get_config()->upstream_frame_debug) {
+    verbose_on_frame_recv_callback(session, frame, user_data);
+  }
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+
+  switch (frame->hd.type) {
+  case NGHTTP2_DATA: {
+    auto downstream = upstream->find_downstream(frame->hd.stream_id);
+    if (!downstream) {
+      return 0;
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      downstream->disable_upstream_rtimer();
+
+      if (!downstream->validate_request_bodylen()) {
+        upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+        return 0;
+      }
+
+      downstream->end_upload_data();
+      downstream->set_request_state(Downstream::MSG_COMPLETE);
+    }
+
+    break;
+  }
+  case NGHTTP2_HEADERS: {
+    auto downstream = upstream->find_downstream(frame->hd.stream_id);
+    if (!downstream) {
+      return 0;
+    }
+
+    if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
+      downstream->reset_upstream_rtimer();
+
+      return on_request_headers(upstream, downstream, session, frame);
+    }
+
+    if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      if (!downstream->validate_request_bodylen()) {
+        upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+        return 0;
+      }
+
+      downstream->disable_upstream_rtimer();
+
+      downstream->end_upload_data();
+      downstream->set_request_state(Downstream::MSG_COMPLETE);
+    } else {
+      rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
+                                     frame->hd.stream_id,
+                                     NGHTTP2_PROTOCOL_ERROR);
+      if (rv != 0) {
+        return NGHTTP2_ERR_CALLBACK_FAILURE;
+      }
+    }
+
+    break;
+  }
+  case NGHTTP2_PRIORITY: {
+    // TODO comment out for now
+    // rv = downstream->change_priority(frame->priority.pri);
+    // if(rv != 0) {
+    //   return NGHTTP2_ERR_CALLBACK_FAILURE;
+    // }
+    break;
+  }
+  case NGHTTP2_SETTINGS:
+    if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+      break;
+    }
+    upstream->stop_settings_timer();
+    break;
+  case NGHTTP2_GOAWAY:
+    if (LOG_ENABLED(INFO)) {
+      auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
+                                         frame->goaway.opaque_data_len);
+
+      ULOG(INFO, upstream) << "GOAWAY received: last-stream-id="
+                           << frame->goaway.last_stream_id
+                           << ", error_code=" << frame->goaway.error_code
+                           << ", debug_data=" << debug_data;
+    }
+    break;
+  default:
+    break;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) {
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+  auto downstream = upstream->find_downstream(stream_id);
+
+  if (!downstream || !downstream->get_downstream_connection()) {
+    if (upstream->consume(stream_id, len) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+
+  downstream->reset_upstream_rtimer();
+
+  if (downstream->push_upload_data_chunk(data, len) != 0) {
+    upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+
+    if (upstream->consume(stream_id, len) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+
+    return 0;
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
+                           void *user_data) {
+  if (get_config()->upstream_frame_debug) {
+    verbose_on_frame_send_callback(session, frame, user_data);
+  }
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+
+  switch (frame->hd.type) {
+  case NGHTTP2_SETTINGS:
+    if ((frame->hd.flags & NGHTTP2_FLAG_ACK) == 0) {
+      upstream->start_settings_timer();
+    }
+    break;
+  case NGHTTP2_PUSH_PROMISE: {
+    auto downstream = make_unique<Downstream>(
+        upstream, frame->push_promise.promised_stream_id, 0);
+
+    downstream->disable_upstream_rtimer();
+
+    downstream->set_request_major(2);
+    downstream->set_request_minor(0);
+
+    for (size_t i = 0; i < frame->push_promise.nvlen; ++i) {
+      auto &nv = frame->push_promise.nva[i];
+      auto token = http2::lookup_token(nv.name, nv.namelen);
+      switch (token) {
+      case http2::HD__METHOD:
+        downstream->set_request_method({nv.value, nv.value + nv.valuelen});
+        break;
+      case http2::HD__SCHEME:
+        downstream->set_request_http2_scheme(
+            {nv.value, nv.value + nv.valuelen});
+        break;
+      case http2::HD__AUTHORITY:
+        downstream->set_request_http2_authority(
+            {nv.value, nv.value + nv.valuelen});
+        break;
+      case http2::HD__PATH:
+        downstream->set_request_path({nv.value, nv.value + nv.valuelen});
+        break;
+      }
+      downstream->add_request_header(nv.name, nv.namelen, nv.value, nv.valuelen,
+                                     nv.flags & NGHTTP2_NV_FLAG_NO_INDEX,
+                                     token);
+    }
+
+    downstream->inspect_http2_request();
+
+    downstream->set_request_state(Downstream::MSG_COMPLETE);
+
+    // a bit weird but start_downstream() expects that given
+    // downstream is in pending queue.
+    auto ptr = downstream.get();
+    upstream->add_pending_downstream(std::move(downstream));
+    upstream->start_downstream(ptr);
+
+    break;
+  }
+  case NGHTTP2_GOAWAY:
+    if (LOG_ENABLED(INFO)) {
+      auto debug_data = util::ascii_dump(frame->goaway.opaque_data,
+                                         frame->goaway.opaque_data_len);
+
+      ULOG(INFO, upstream) << "Sending GOAWAY: last-stream-id="
+                           << frame->goaway.last_stream_id
+                           << ", error_code=" << frame->goaway.error_code
+                           << ", debug_data=" << debug_data;
+    }
+    break;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int on_frame_not_send_callback(nghttp2_session *session,
+                               const nghttp2_frame *frame, int lib_error_code,
+                               void *user_data) {
+  auto upstream = static_cast<Http2Upstream *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Failed to send control frame type="
+                         << static_cast<uint32_t>(frame->hd.type)
+                         << ", lib_error_code=" << lib_error_code << ":"
+                         << nghttp2_strerror(lib_error_code);
+  }
+  if (frame->hd.type == NGHTTP2_HEADERS &&
+      frame->headers.cat == NGHTTP2_HCAT_RESPONSE &&
+      lib_error_code != NGHTTP2_ERR_STREAM_CLOSED &&
+      lib_error_code != NGHTTP2_ERR_STREAM_CLOSING) {
+    // To avoid stream hanging around, issue RST_STREAM.
+    auto downstream = upstream->find_downstream(frame->hd.stream_id);
+    if (downstream) {
+      upstream->rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+    }
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) {
+  // NGHTTP2_REFUSED_STREAM is important because it tells upstream
+  // client to retry.
+  switch (downstream_error_code) {
+  case NGHTTP2_NO_ERROR:
+  case NGHTTP2_REFUSED_STREAM:
+    return downstream_error_code;
+  default:
+    return NGHTTP2_INTERNAL_ERROR;
+  }
+}
+} // namespace
+
+namespace {
+void settings_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto upstream = static_cast<Http2Upstream *>(w->data);
+  auto handler = upstream->get_client_handler();
+  ULOG(INFO, upstream) << "SETTINGS timeout";
+  if (upstream->terminate_session(NGHTTP2_SETTINGS_TIMEOUT) != 0) {
+    delete handler;
+    return;
+  }
+  handler->signal_write();
+}
+} // namespace
+
+namespace {
+void shutdown_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto upstream = static_cast<Http2Upstream *>(w->data);
+  auto handler = upstream->get_client_handler();
+  upstream->submit_goaway();
+  handler->signal_write();
+}
+} // namespace
+
+namespace {
+void prepare_cb(struct ev_loop *loop, ev_prepare *w, int revents) {
+  auto upstream = static_cast<Http2Upstream *>(w->data);
+  upstream->check_shutdown();
+}
+} // namespace
+
+void Http2Upstream::submit_goaway() {
+  auto last_stream_id = nghttp2_session_get_last_proc_stream_id(session_);
+  nghttp2_submit_goaway(session_, NGHTTP2_FLAG_NONE, last_stream_id,
+                        NGHTTP2_NO_ERROR, nullptr, 0);
+}
+
+void Http2Upstream::check_shutdown() {
+  int rv;
+  if (shutdown_handled_) {
+    return;
+  }
+  if (worker_config->graceful_shutdown) {
+    shutdown_handled_ = true;
+    rv = nghttp2_submit_shutdown_notice(session_);
+    if (rv != 0) {
+      ULOG(FATAL, this) << "nghttp2_submit_shutdown_notice() failed: "
+                        << nghttp2_strerror(rv);
+      return;
+    }
+    handler_->signal_write();
+    ev_timer_start(handler_->get_loop(), &shutdown_timer_);
+  }
+}
+
+Http2Upstream::Http2Upstream(ClientHandler *handler)
+    : downstream_queue_(
+          get_config()->http2_proxy
+              ? get_config()->downstream_connections_per_host
+              : get_config()->downstream_proto == PROTO_HTTP
+                    ? get_config()->downstream_connections_per_frontend
+                    : 0,
+          !get_config()->http2_proxy),
+      handler_(handler), session_(nullptr), data_pending_(nullptr),
+      data_pendinglen_(0), shutdown_handled_(false) {
+
+  int rv;
+
+  nghttp2_session_callbacks *callbacks;
+  rv = nghttp2_session_callbacks_new(&callbacks);
+
+  assert(rv == 0);
+
+  auto callbacks_deleter = defer(nghttp2_session_callbacks_del, callbacks);
+
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+      callbacks, on_stream_close_callback);
+
+  nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks,
+                                                       on_frame_recv_callback);
+
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+      callbacks, on_data_chunk_recv_callback);
+
+  nghttp2_session_callbacks_set_on_frame_send_callback(callbacks,
+                                                       on_frame_send_callback);
+
+  nghttp2_session_callbacks_set_on_frame_not_send_callback(
+      callbacks, on_frame_not_send_callback);
+
+  nghttp2_session_callbacks_set_on_header_callback(callbacks,
+                                                   on_header_callback);
+
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+      callbacks, on_begin_headers_callback);
+
+  if (get_config()->padding) {
+    nghttp2_session_callbacks_set_select_padding_callback(
+        callbacks, http::select_padding_callback);
+  }
+
+  rv = nghttp2_session_server_new2(&session_, callbacks, this,
+                                   get_config()->http2_option);
+
+  assert(rv == 0);
+
+  flow_control_ = true;
+
+  // TODO Maybe call from outside?
+  std::array<nghttp2_settings_entry, 2> entry;
+  entry[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  entry[0].value = get_config()->http2_max_concurrent_streams;
+
+  entry[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  entry[1].value = (1 << get_config()->http2_upstream_window_bits) - 1;
+
+  rv = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(),
+                               entry.size());
+  if (rv != 0) {
+    ULOG(ERROR, this) << "nghttp2_submit_settings() returned error: "
+                      << nghttp2_strerror(rv);
+  }
+
+  if (get_config()->http2_upstream_connection_window_bits > 16) {
+    int32_t delta = (1 << get_config()->http2_upstream_connection_window_bits) -
+                    1 - NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE;
+    rv = nghttp2_submit_window_update(session_, NGHTTP2_FLAG_NONE, 0, delta);
+
+    if (rv != 0) {
+      ULOG(ERROR, this) << "nghttp2_submit_window_update() returned error: "
+                        << nghttp2_strerror(rv);
+    }
+  }
+
+  if (!get_config()->altsvcs.empty()) {
+    // Set max_age to 24hrs, which is default for alt-svc header
+    // field.
+    for (auto &altsvc : get_config()->altsvcs) {
+      rv = nghttp2_submit_altsvc(
+          session_, NGHTTP2_FLAG_NONE, 0, 86400, altsvc.port,
+          reinterpret_cast<const uint8_t *>(altsvc.protocol_id),
+          altsvc.protocol_id_len,
+          reinterpret_cast<const uint8_t *>(altsvc.host), altsvc.host_len,
+          reinterpret_cast<const uint8_t *>(altsvc.origin), altsvc.origin_len);
+
+      if (rv != 0) {
+        ULOG(ERROR, this) << "nghttp2_submit_altsvc() returned error: "
+                          << nghttp2_strerror(rv);
+      }
+    }
+  }
+
+  // We wait for SETTINGS ACK at least 10 seconds.
+  ev_timer_init(&settings_timer_, settings_timeout_cb, 10., 0.);
+
+  settings_timer_.data = this;
+
+  // timer for 2nd GOAWAY.  HTTP/2 spec recommend 1 RTT.  We wait for
+  // 2 seconds.
+  ev_timer_init(&shutdown_timer_, shutdown_timeout_cb, 2., 0);
+  shutdown_timer_.data = this;
+
+  ev_prepare_init(&prep_, prepare_cb);
+  prep_.data = this;
+  ev_prepare_start(handler_->get_loop(), &prep_);
+
+  handler_->reset_upstream_read_timeout(
+      get_config()->http2_upstream_read_timeout);
+
+  handler_->signal_write();
+}
+
+Http2Upstream::~Http2Upstream() {
+  nghttp2_session_del(session_);
+  ev_prepare_stop(handler_->get_loop(), &prep_);
+  ev_timer_stop(handler_->get_loop(), &shutdown_timer_);
+  ev_timer_stop(handler_->get_loop(), &settings_timer_);
+}
+
+int Http2Upstream::on_read() {
+  ssize_t rv = 0;
+  auto rb = handler_->get_rb();
+
+  if (rb->rleft()) {
+    rv = nghttp2_session_mem_recv(session_, rb->pos, rb->rleft());
+    if (rv < 0) {
+      if (rv != NGHTTP2_ERR_BAD_PREFACE) {
+        ULOG(ERROR, this) << "nghttp2_session_recv() returned error: "
+                          << nghttp2_strerror(rv);
+      }
+      return -1;
+    }
+
+    // nghttp2_session_mem_recv should consume all input bytes on
+    // success.
+    assert(static_cast<size_t>(rv) == rb->rleft());
+    rb->reset();
+  }
+
+  auto wb = handler_->get_wb();
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "No more read/write for this HTTP2 session";
+    }
+    return -1;
+  }
+
+  handler_->signal_write();
+  return 0;
+}
+
+// After this function call, downstream may be deleted.
+int Http2Upstream::on_write() {
+  auto wb = handler_->get_wb();
+
+  if (data_pending_) {
+    auto n = std::min(wb->wleft(), data_pendinglen_);
+    wb->write(data_pending_, n);
+    if (n < data_pendinglen_) {
+      data_pending_ += n;
+      data_pendinglen_ -= n;
+      return 0;
+    }
+
+    data_pending_ = nullptr;
+    data_pendinglen_ = 0;
+  }
+
+  for (;;) {
+    const uint8_t *data;
+    auto datalen = nghttp2_session_mem_send(session_, &data);
+
+    if (datalen < 0) {
+      ULOG(ERROR, this) << "nghttp2_session_mem_send() returned error: "
+                        << nghttp2_strerror(datalen);
+      return -1;
+    }
+    if (datalen == 0) {
+      break;
+    }
+    auto n = wb->write(data, datalen);
+    if (n < static_cast<decltype(n)>(datalen)) {
+      data_pending_ = data + n;
+      data_pendinglen_ = datalen - n;
+      return 0;
+    }
+  }
+
+  if (nghttp2_session_want_read(session_) == 0 &&
+      nghttp2_session_want_write(session_) == 0 && wb->rleft() == 0) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "No more read/write for this HTTP2 session";
+    }
+    return -1;
+  }
+
+  return 0;
+}
+
+ClientHandler *Http2Upstream::get_client_handler() const { return handler_; }
+
+int Http2Upstream::downstream_read(DownstreamConnection *dconn) {
+  auto downstream = dconn->get_downstream();
+
+  if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
+    // If upstream HTTP2 stream was closed, we just close downstream,
+    // because there is no consumer now. Downstream connection is also
+    // closed in this case.
+    remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  if (downstream->get_response_state() == Downstream::MSG_RESET) {
+    // The downstream stream was reset (canceled). In this case,
+    // RST_STREAM to the upstream and delete downstream connection
+    // here. Deleting downstream will be taken place at
+    // on_stream_close_callback.
+    rst_stream(downstream,
+               infer_upstream_rst_stream_error_code(
+                   downstream->get_response_rst_stream_error_code()));
+    downstream->pop_downstream_connection();
+    // dconn was deleted
+    dconn = nullptr;
+  } else if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) {
+    if (error_reply(downstream, 502) != 0) {
+      return -1;
+    }
+    downstream->pop_downstream_connection();
+    // dconn was deleted
+    dconn = nullptr;
+  } else {
+    auto rv = downstream->on_read();
+    if (rv == SHRPX_ERR_EOF) {
+      return downstream_eof(dconn);
+    }
+    if (rv != 0) {
+      if (rv != SHRPX_ERR_NETWORK) {
+        if (LOG_ENABLED(INFO)) {
+          DCLOG(INFO, dconn) << "HTTP parser failure";
+        }
+      }
+      return downstream_error(dconn, Downstream::EVENT_ERROR);
+    }
+    // Detach downstream connection early so that it could be reused
+    // without hitting server's request timeout.
+    if (downstream->get_response_state() == Downstream::MSG_COMPLETE &&
+        !downstream->get_response_connection_close()) {
+      // Keep-alive
+      downstream->detach_downstream_connection();
+    }
+  }
+
+  handler_->signal_write();
+
+  // At this point, downstream may be deleted.
+
+  return 0;
+}
+
+int Http2Upstream::downstream_write(DownstreamConnection *dconn) {
+  int rv;
+  rv = dconn->on_write();
+  if (rv == SHRPX_ERR_NETWORK) {
+    return downstream_error(dconn, Downstream::EVENT_ERROR);
+  }
+  if (rv != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+int Http2Upstream::downstream_eof(DownstreamConnection *dconn) {
+  auto downstream = dconn->get_downstream();
+
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
+  }
+  if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
+    // If stream was closed already, we don't need to send reply at
+    // the first place. We can delete downstream.
+    remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  // Delete downstream connection. If we don't delete it here, it will
+  // be pooled in on_stream_close_callback.
+  downstream->pop_downstream_connection();
+  // dconn was deleted
+  dconn = nullptr;
+  // downstream wil be deleted in on_stream_close_callback.
+  if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+    // Server may indicate the end of the request by EOF
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "Downstream body was ended by EOF";
+    }
+    downstream->set_response_state(Downstream::MSG_COMPLETE);
+
+    // For tunneled connection, MSG_COMPLETE signals
+    // downstream_data_read_callback to send RST_STREAM after pending
+    // response body is sent. This is needed to ensure that RST_STREAM
+    // is sent after all pending data are sent.
+    on_downstream_body_complete(downstream);
+  } else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
+    // If stream was not closed, then we set MSG_COMPLETE and let
+    // on_stream_close_callback delete downstream.
+    if (error_reply(downstream, 502) != 0) {
+      return -1;
+    }
+  }
+  handler_->signal_write();
+  // At this point, downstream may be deleted.
+  return 0;
+}
+
+int Http2Upstream::downstream_error(DownstreamConnection *dconn, int events) {
+  auto downstream = dconn->get_downstream();
+
+  if (LOG_ENABLED(INFO)) {
+    if (events & Downstream::EVENT_ERROR) {
+      DCLOG(INFO, dconn) << "Downstream network/general error";
+    } else {
+      DCLOG(INFO, dconn) << "Timeout";
+    }
+    if (downstream->get_upgraded()) {
+      DCLOG(INFO, dconn) << "Note: this is tunnel connection";
+    }
+  }
+
+  if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
+    remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  // Delete downstream connection. If we don't delete it here, it will
+  // be pooled in on_stream_close_callback.
+  downstream->pop_downstream_connection();
+  // dconn was deleted
+  dconn = nullptr;
+
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    // For SSL tunneling, we issue RST_STREAM. For other types of
+    // stream, we don't have to do anything since response was
+    // complete.
+    if (downstream->get_upgraded()) {
+      rst_stream(downstream, NGHTTP2_NO_ERROR);
+    }
+  } else {
+    if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+      if (downstream->get_upgraded()) {
+        on_downstream_body_complete(downstream);
+      } else {
+        rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+      }
+    } else {
+      unsigned int status;
+      if (events & Downstream::EVENT_TIMEOUT) {
+        status = 504;
+      } else {
+        status = 502;
+      }
+      if (error_reply(downstream, status) != 0) {
+        return -1;
+      }
+    }
+    downstream->set_response_state(Downstream::MSG_COMPLETE);
+  }
+  handler_->signal_write();
+  // At this point, downstream may be deleted.
+  return 0;
+}
+
+int Http2Upstream::rst_stream(Downstream *downstream, uint32_t error_code) {
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, this) << "RST_STREAM stream_id=" << downstream->get_stream_id()
+                     << " with error_code=" << error_code;
+  }
+  int rv;
+  rv = nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE,
+                                 downstream->get_stream_id(), error_code);
+  if (rv < NGHTTP2_ERR_FATAL) {
+    ULOG(FATAL, this) << "nghttp2_submit_rst_stream() failed: "
+                      << nghttp2_strerror(rv);
+    DIE();
+  }
+  return 0;
+}
+
+int Http2Upstream::terminate_session(uint32_t error_code) {
+  int rv;
+  rv = nghttp2_session_terminate_session(session_, error_code);
+  if (rv != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+namespace {
+ssize_t downstream_data_read_callback(nghttp2_session *session,
+                                      int32_t stream_id, uint8_t *buf,
+                                      size_t length, uint32_t *data_flags,
+                                      nghttp2_data_source *source,
+                                      void *user_data) {
+  auto downstream = static_cast<Downstream *>(source->ptr);
+  auto upstream = static_cast<Http2Upstream *>(downstream->get_upstream());
+  auto body = downstream->get_response_buf();
+  assert(body);
+
+  auto dconn = downstream->get_downstream_connection();
+
+  if (body->rleft() == 0 && dconn &&
+      downstream->get_response_state() != Downstream::MSG_COMPLETE) {
+    // Try to read more if buffer is empty.  This will help small
+    // buffer and make priority handling a bit better.
+    if (upstream->downstream_read(dconn) != 0) {
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
+
+  auto nread = body->remove(buf, length);
+  auto body_empty = body->rleft() == 0;
+
+  if (body_empty &&
+      downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+
+    if (!downstream->get_upgraded()) {
+
+      if (nghttp2_session_get_stream_remote_close(session, stream_id) == 0) {
+        upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
+      }
+    } else {
+      // For tunneling, issue RST_STREAM to finish the stream.
+      if (LOG_ENABLED(INFO)) {
+        ULOG(INFO, upstream)
+            << "RST_STREAM to tunneled stream stream_id=" << stream_id;
+      }
+      upstream->rst_stream(downstream, NGHTTP2_NO_ERROR);
+    }
+  }
+
+  if (body_empty) {
+    downstream->disable_upstream_wtimer();
+  } else {
+    downstream->reset_upstream_wtimer();
+  }
+
+  if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) {
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  if (nread == 0 && ((*data_flags) & NGHTTP2_DATA_FLAG_EOF) == 0) {
+    return NGHTTP2_ERR_DEFERRED;
+  }
+
+  if (nread > 0) {
+    downstream->add_response_sent_bodylen(nread);
+  }
+
+  return nread;
+}
+} // namespace
+
+int Http2Upstream::error_reply(Downstream *downstream,
+                               unsigned int status_code) {
+  int rv;
+  auto html = http::create_error_html(status_code);
+  downstream->set_response_http_status(status_code);
+  auto body = downstream->get_response_buf();
+  body->append(html.c_str(), html.size());
+  downstream->set_response_state(Downstream::MSG_COMPLETE);
+
+  nghttp2_data_provider data_prd;
+  data_prd.source.ptr = downstream;
+  data_prd.read_callback = downstream_data_read_callback;
+
+  auto content_length = util::utos(html.size());
+  auto status_code_str = util::utos(status_code);
+  auto nva =
+      make_array(http2::make_nv_ls(":status", status_code_str),
+                 http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
+                 http2::make_nv_lc("server", get_config()->server_name),
+                 http2::make_nv_ls("content-length", content_length));
+
+  rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
+                               nva.data(), nva.size(), &data_prd);
+  if (rv < NGHTTP2_ERR_FATAL) {
+    ULOG(FATAL, this) << "nghttp2_submit_response() failed: "
+                      << nghttp2_strerror(rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+void
+Http2Upstream::add_pending_downstream(std::unique_ptr<Downstream> downstream) {
+  downstream_queue_.add_pending(std::move(downstream));
+}
+
+void Http2Upstream::remove_downstream(Downstream *downstream) {
+  if (downstream->accesslog_ready()) {
+    handler_->write_accesslog(downstream);
+  }
+
+  auto next_downstream =
+      downstream_queue_.remove_and_pop_blocked(downstream->get_stream_id());
+
+  if (next_downstream) {
+    initiate_downstream(std::move(next_downstream));
+  }
+}
+
+Downstream *Http2Upstream::find_downstream(int32_t stream_id) {
+  return downstream_queue_.find(stream_id);
+}
+
+nghttp2_session *Http2Upstream::get_http2_session() { return session_; }
+
+// WARNING: Never call directly or indirectly nghttp2_session_send or
+// nghttp2_session_recv. These calls may delete downstream.
+int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
+  int rv;
+
+  if (LOG_ENABLED(INFO)) {
+    if (downstream->get_non_final_response()) {
+      DLOG(INFO, downstream) << "HTTP non-final response header";
+    } else {
+      DLOG(INFO, downstream) << "HTTP response header completed";
+    }
+  }
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy &&
+      !get_config()->no_location_rewrite) {
+    downstream->rewrite_location_response_header(
+        downstream->get_request_http2_scheme());
+  }
+
+  size_t nheader = downstream->get_response_headers().size();
+  auto nva = std::vector<nghttp2_nv>();
+  // 3 means :status and possible server and via header field.
+  nva.reserve(nheader + 3 + get_config()->add_response_headers.size());
+  std::string via_value;
+  auto response_status = util::utos(downstream->get_response_http_status());
+  nva.push_back(http2::make_nv_ls(":status", response_status));
+
+  http2::copy_headers_to_nva(nva, downstream->get_response_headers());
+
+  if (downstream->get_non_final_response()) {
+    if (LOG_ENABLED(INFO)) {
+      log_response_headers(downstream, nva);
+    }
+
+    rv = nghttp2_submit_headers(session_, NGHTTP2_FLAG_NONE,
+                                downstream->get_stream_id(), nullptr,
+                                nva.data(), nva.size(), nullptr);
+
+    downstream->clear_response_headers();
+
+    if (rv != 0) {
+      ULOG(FATAL, this) << "nghttp2_submit_headers() failed";
+      return -1;
+    }
+
+    return 0;
+  }
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy) {
+    nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
+  } else {
+    auto server = downstream->get_response_header(http2::HD_SERVER);
+    if (server) {
+      nva.push_back(http2::make_nv_ls("server", (*server).value));
+    }
+  }
+
+  auto via = downstream->get_response_header(http2::HD_VIA);
+  if (get_config()->no_via) {
+    if (via) {
+      nva.push_back(http2::make_nv_ls("via", (*via).value));
+    }
+  } else {
+    if (via) {
+      via_value = (*via).value;
+      via_value += ", ";
+    }
+    via_value += http::create_via_header_value(
+        downstream->get_response_major(), downstream->get_response_minor());
+    nva.push_back(http2::make_nv_ls("via", via_value));
+  }
+
+  for (auto &p : get_config()->add_response_headers) {
+    nva.push_back(http2::make_nv(p.first, p.second));
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    log_response_headers(downstream, nva);
+  }
+
+  if (get_config()->http2_upstream_dump_response_header) {
+    http2::dump_nv(get_config()->http2_upstream_dump_response_header,
+                   nva.data(), nva.size());
+  }
+
+  nghttp2_data_provider data_prd;
+  data_prd.source.ptr = downstream;
+  data_prd.read_callback = downstream_data_read_callback;
+
+  nghttp2_data_provider *data_prdptr;
+
+  if (downstream->expect_response_body()) {
+    data_prdptr = &data_prd;
+  } else {
+    data_prdptr = nullptr;
+  }
+
+  rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
+                               nva.data(), nva.size(), data_prdptr);
+  if (rv != 0) {
+    ULOG(FATAL, this) << "nghttp2_submit_response() failed";
+    return -1;
+  }
+
+  // We need some conditions that must be fulfilled to initiate server
+  // push.
+  //
+  // * Server push is disabled for http2 proxy, since incoming headers
+  //   are mixed origins.  We don't know how to reliably determine the
+  //   authority yet.
+  //
+  // * If downstream is http/2, it is likely that PUSH_PROMISE is
+  //   coming from there, so we don't initiate PUSH_RPOMISE here.
+  //
+  // * We need 200 response code for associated resource.  This is too
+  //   restrictive, we will review this later.
+  //
+  // * We requires GET or POST for associated resource.  Probably we
+  //   don't want to push for HEAD request.  Not sure other methods
+  //   are also eligible for push.
+  if (!get_config()->no_server_push &&
+      get_config()->downstream_proto == PROTO_HTTP &&
+      !get_config()->http2_proxy && (downstream->get_stream_id() % 2) &&
+      downstream->get_response_header(http2::HD_LINK) &&
+      downstream->get_response_http_status() == 200 &&
+      (downstream->get_request_method() == "GET" ||
+       downstream->get_request_method() == "POST")) {
+
+    if (prepare_push_promise(downstream) != 0) {
+      return -1;
+    }
+  }
+
+  return 0;
+}
+
+// WARNING: Never call directly or indirectly nghttp2_session_send or
+// nghttp2_session_recv. These calls may delete downstream.
+int Http2Upstream::on_downstream_body(Downstream *downstream,
+                                      const uint8_t *data, size_t len,
+                                      bool flush) {
+  auto body = downstream->get_response_buf();
+  body->append(data, len);
+
+  if (flush) {
+    nghttp2_session_resume_data(session_, downstream->get_stream_id());
+
+    downstream->ensure_upstream_wtimer();
+  }
+
+  return 0;
+}
+
+// WARNING: Never call directly or indirectly nghttp2_session_send or
+// nghttp2_session_recv. These calls may delete downstream.
+int Http2Upstream::on_downstream_body_complete(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, downstream) << "HTTP response completed";
+  }
+
+  if (!downstream->validate_response_bodylen()) {
+    rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
+    downstream->set_response_connection_close(true);
+    return 0;
+  }
+
+  nghttp2_session_resume_data(session_, downstream->get_stream_id());
+  downstream->ensure_upstream_wtimer();
+
+  return 0;
+}
+
+bool Http2Upstream::get_flow_control() const { return flow_control_; }
+
+void Http2Upstream::pause_read(IOCtrlReason reason) {}
+
+int Http2Upstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+                               size_t consumed) {
+  if (get_flow_control()) {
+    assert(downstream->get_request_datalen() >= consumed);
+
+    if (consume(downstream->get_stream_id(), consumed) != 0) {
+      return -1;
+    }
+
+    downstream->dec_request_datalen(consumed);
+  }
+
+  handler_->signal_write();
+  return 0;
+}
+
+int Http2Upstream::on_downstream_abort_request(Downstream *downstream,
+                                               unsigned int status_code) {
+  int rv;
+
+  rv = error_reply(downstream, status_code);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  handler_->signal_write();
+  return 0;
+}
+
+int Http2Upstream::consume(int32_t stream_id, size_t len) {
+  int rv;
+
+  rv = nghttp2_session_consume(session_, stream_id, len);
+
+  if (rv != 0) {
+    ULOG(WARN, this) << "nghttp2_session_consume() returned error: "
+                     << nghttp2_strerror(rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+void
+Http2Upstream::log_response_headers(Downstream *downstream,
+                                    const std::vector<nghttp2_nv> &nva) const {
+  std::stringstream ss;
+  for (auto &nv : nva) {
+    ss << TTY_HTTP_HD;
+    ss.write(reinterpret_cast<const char *>(nv.name), nv.namelen);
+    ss << TTY_RST << ": ";
+    ss.write(reinterpret_cast<const char *>(nv.value), nv.valuelen);
+    ss << "\n";
+  }
+  ULOG(INFO, this) << "HTTP response headers. stream_id="
+                   << downstream->get_stream_id() << "\n" << ss.str();
+}
+
+int Http2Upstream::on_timeout(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, this) << "Stream timeout stream_id="
+                     << downstream->get_stream_id();
+  }
+
+  rst_stream(downstream, NGHTTP2_NO_ERROR);
+
+  return 0;
+}
+
+void Http2Upstream::on_handler_delete() {
+  for (auto &ent : downstream_queue_.get_active_downstreams()) {
+    if (ent.second->accesslog_ready()) {
+      handler_->write_accesslog(ent.second.get());
+    }
+  }
+}
+
+int Http2Upstream::on_downstream_reset(bool no_retry) {
+  int rv;
+
+  for (auto &ent : downstream_queue_.get_active_downstreams()) {
+    auto downstream = ent.second.get();
+    if ((downstream->get_request_state() != Downstream::HEADER_COMPLETE &&
+         downstream->get_request_state() != Downstream::MSG_COMPLETE) ||
+        downstream->get_response_state() != Downstream::INITIAL) {
+      rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+      downstream->pop_downstream_connection();
+      continue;
+    }
+
+    downstream->pop_downstream_connection();
+
+    downstream->add_retry();
+
+    if (no_retry || downstream->no_more_retry()) {
+      if (on_downstream_abort_request(downstream, 503) != 0) {
+        return -1;
+      }
+      continue;
+    }
+
+    // downstream connection is clean; we can retry with new
+    // downstream connection.
+
+    rv = downstream->attach_downstream_connection(
+        handler_->get_downstream_connection());
+    if (rv != 0) {
+      rst_stream(downstream, NGHTTP2_INTERNAL_ERROR);
+      downstream->pop_downstream_connection();
+      continue;
+    }
+  }
+
+  handler_->signal_write();
+
+  return 0;
+}
+
+MemchunkPool *Http2Upstream::get_mcpool() { return &mcpool_; }
+
+int Http2Upstream::prepare_push_promise(Downstream *downstream) {
+  int rv;
+  http_parser_url u;
+  memset(&u, 0, sizeof(u));
+  rv = http_parser_parse_url(downstream->get_request_path().c_str(),
+                             downstream->get_request_path().size(), 0, &u);
+  if (rv != 0) {
+    return 0;
+  }
+  const char *base;
+  size_t baselen;
+  if (u.field_set & (1 << UF_PATH)) {
+    auto &f = u.field_data[UF_PATH];
+    base = downstream->get_request_path().c_str() + f.off;
+    baselen = f.len;
+  } else {
+    base = "/";
+    baselen = 1;
+  }
+  for (auto &kv : downstream->get_response_headers()) {
+    if (kv.token != http2::HD_LINK) {
+      continue;
+    }
+    for (auto &link :
+         http2::parse_link_header(kv.value.c_str(), kv.value.size())) {
+      auto link_url = link.uri.first;
+      auto link_urllen = link.uri.second - link.uri.first;
+
+      const char *rel;
+      size_t rellen;
+      const char *relq = nullptr;
+      size_t relqlen = 0;
+
+      http_parser_url v;
+      memset(&v, 0, sizeof(v));
+      rv = http_parser_parse_url(link_url, link_urllen, 0, &v);
+      if (rv != 0) {
+        assert(link_urllen);
+        if (link_url[0] == '/') {
+          continue;
+        }
+        // treat link_url as relative URI.
+        auto end = std::find(link_url, link_url + link_urllen, '#');
+        auto q = std::find(link_url, end, '?');
+        rel = link_url;
+        rellen = q - link_url;
+        if (q != end) {
+          relq = q + 1;
+          relqlen = end - relq;
+        }
+      } else {
+        if (v.field_set & (1 << UF_HOST)) {
+          continue;
+        }
+        if (v.field_set & (1 << UF_PATH)) {
+          auto &f = v.field_data[UF_PATH];
+          rel = link_url + f.off;
+          rellen = f.len;
+        } else {
+          rel = "/";
+          rellen = 1;
+        }
+
+        if (v.field_set & (1 << UF_QUERY)) {
+          auto &f = v.field_data[UF_QUERY];
+          relq = link_url + f.off;
+          relqlen = f.len;
+        }
+      }
+      auto path = http2::path_join(base, baselen, nullptr, 0, rel, rellen, relq,
+                                   relqlen);
+      rv = submit_push_promise(path, downstream);
+      if (rv != 0) {
+        return -1;
+      }
+    }
+  }
+  return 0;
+}
+
+int Http2Upstream::submit_push_promise(const std::string &path,
+                                       Downstream *downstream) {
+  int rv;
+  std::vector<nghttp2_nv> nva;
+  nva.reserve(downstream->get_request_headers().size());
+  for (auto &kv : downstream->get_request_headers()) {
+    switch (kv.token) {
+    // TODO generate referer
+    case http2::HD__AUTHORITY:
+    case http2::HD__SCHEME:
+    case http2::HD_ACCEPT_ENCODING:
+    case http2::HD_ACCEPT_LANGUAGE:
+    case http2::HD_CACHE_CONTROL:
+    case http2::HD_HOST:
+    case http2::HD_USER_AGENT:
+      nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
+      break;
+    case http2::HD__METHOD:
+      // juse use "GET" for now
+      nva.push_back(http2::make_nv_lc(":method", "GET"));
+      continue;
+    case http2::HD__PATH:
+      nva.push_back(http2::make_nv_ls(":path", path));
+      continue;
+    }
+  }
+
+  rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE,
+                                   downstream->get_stream_id(), nva.data(),
+                                   nva.size(), nullptr);
+
+  if (rv < 0) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "nghttp2_submit_push_promise() failed: "
+                       << nghttp2_strerror(rv);
+    }
+    if (nghttp2_is_fatal(rv)) {
+      return -1;
+    }
+    return 0;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    std::stringstream ss;
+    for (auto &nv : nva) {
+      ss << TTY_HTTP_HD << nv.name << TTY_RST << ": " << nv.value << "\n";
+    }
+    ULOG(INFO, this) << "HTTP push request headers. promised_stream_id=" << rv
+                     << "\n" << ss.str();
+  }
+
+  return 0;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h
new file mode 100644 (file)
index 0000000..1278ebc
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP2_UPSTREAM_H
+#define SHRPX_HTTP2_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <ev.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream_queue.h"
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class ClientHandler;
+class HttpsUpstream;
+
+class Http2Upstream : public Upstream {
+public:
+  Http2Upstream(ClientHandler *handler);
+  virtual ~Http2Upstream();
+  virtual int on_read();
+  virtual int on_write();
+  virtual int on_timeout(Downstream *downstream);
+  virtual int on_downstream_abort_request(Downstream *downstream,
+                                          unsigned int status_code);
+  virtual ClientHandler *get_client_handler() const;
+
+  virtual int downstream_read(DownstreamConnection *dconn);
+  virtual int downstream_write(DownstreamConnection *dconn);
+  virtual int downstream_eof(DownstreamConnection *dconn);
+  virtual int downstream_error(DownstreamConnection *dconn, int events);
+
+  void add_pending_downstream(std::unique_ptr<Downstream> downstream);
+  void remove_downstream(Downstream *downstream);
+  Downstream *find_downstream(int32_t stream_id);
+
+  nghttp2_session *get_http2_session();
+
+  int rst_stream(Downstream *downstream, uint32_t error_code);
+  int terminate_session(uint32_t error_code);
+  int error_reply(Downstream *downstream, unsigned int status_code);
+
+  virtual void pause_read(IOCtrlReason reason);
+  virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+                          size_t consumed);
+
+  virtual int on_downstream_header_complete(Downstream *downstream);
+  virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+                                 size_t len, bool flush);
+  virtual int on_downstream_body_complete(Downstream *downstream);
+
+  virtual void on_handler_delete();
+  virtual int on_downstream_reset(bool no_retry);
+
+  virtual MemchunkPool *get_mcpool();
+
+  bool get_flow_control() const;
+  // Perform HTTP/2 upgrade from |upstream|. On success, this object
+  // takes ownership of the |upstream|. This function returns 0 if it
+  // succeeds, or -1.
+  int upgrade_upstream(HttpsUpstream *upstream);
+  void start_settings_timer();
+  void stop_settings_timer();
+  int consume(int32_t stream_id, size_t len);
+  void log_response_headers(Downstream *downstream,
+                            const std::vector<nghttp2_nv> &nva) const;
+  void start_downstream(Downstream *downstream);
+  void initiate_downstream(std::unique_ptr<Downstream> downstream);
+
+  void submit_goaway();
+  void check_shutdown();
+
+  int prepare_push_promise(Downstream *downstream);
+  int submit_push_promise(const std::string &path, Downstream *downstream);
+
+private:
+  // must be put before downstream_queue_
+  std::unique_ptr<HttpsUpstream> pre_upstream_;
+  MemchunkPool mcpool_;
+  DownstreamQueue downstream_queue_;
+  ev_timer settings_timer_;
+  ev_timer shutdown_timer_;
+  ev_prepare prep_;
+  ClientHandler *handler_;
+  nghttp2_session *session_;
+  const uint8_t *data_pending_;
+  size_t data_pendinglen_;
+  bool flow_control_;
+  bool shutdown_handled_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP2_UPSTREAM_H
diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc
new file mode 100644 (file)
index 0000000..5dd9015
--- /dev/null
@@ -0,0 +1,776 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_http_downstream_connection.h"
+
+#include "shrpx_client_handler.h"
+#include "shrpx_upstream.h"
+#include "shrpx_downstream.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_http.h"
+#include "shrpx_worker_config.h"
+#include "shrpx_connect_blocker.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "shrpx_worker.h"
+#include "http2.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, dconn) << "Time out";
+  }
+
+  auto downstream = dconn->get_downstream();
+  auto upstream = downstream->get_upstream();
+  auto handler = upstream->get_client_handler();
+
+  // Do this so that dconn is not pooled
+  downstream->set_response_connection_close(true);
+
+  if (upstream->downstream_error(dconn, Downstream::EVENT_TIMEOUT) != 0) {
+    delete handler;
+  }
+}
+} // namespace
+
+namespace {
+void readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+  auto downstream = dconn->get_downstream();
+  auto upstream = downstream->get_upstream();
+  auto handler = upstream->get_client_handler();
+
+  if (upstream->downstream_read(dconn) != 0) {
+    delete handler;
+  }
+}
+} // namespace
+
+namespace {
+void writecb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+  auto downstream = dconn->get_downstream();
+  auto upstream = downstream->get_upstream();
+  auto handler = upstream->get_client_handler();
+
+  if (upstream->downstream_write(dconn) != 0) {
+    delete handler;
+  }
+}
+} // namespace
+
+namespace {
+void connectcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+  auto downstream = dconn->get_downstream();
+  auto upstream = downstream->get_upstream();
+  auto handler = upstream->get_client_handler();
+  if (dconn->on_connect() != 0) {
+    if (upstream->on_downstream_abort_request(downstream, 503) != 0) {
+      delete handler;
+    }
+    return;
+  }
+  writecb(loop, w, revents);
+}
+} // namespace
+
+HttpDownstreamConnection::HttpDownstreamConnection(
+    DownstreamConnectionPool *dconn_pool, struct ev_loop *loop)
+    : DownstreamConnection(dconn_pool),
+      conn_(loop, -1, nullptr, get_config()->downstream_write_timeout,
+            get_config()->downstream_read_timeout, 0, 0, 0, 0, connectcb,
+            readcb, timeoutcb, this),
+      ioctrl_(&conn_.rlimit), response_htp_{0}, addr_idx_(0) {}
+
+HttpDownstreamConnection::~HttpDownstreamConnection() {
+  // Downstream and DownstreamConnection may be deleted
+  // asynchronously.
+  if (downstream_) {
+    downstream_->release_downstream_connection();
+  }
+}
+
+int HttpDownstreamConnection::attach_downstream(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream;
+  }
+
+  if (conn_.fd == -1) {
+    auto connect_blocker = client_handler_->get_http1_connect_blocker();
+
+    if (connect_blocker->blocked()) {
+      if (LOG_ENABLED(INFO)) {
+        DCLOG(INFO, this)
+            << "Downstream connection was blocked by connect_blocker";
+      }
+      return -1;
+    }
+
+    auto worker_stat = client_handler_->get_worker_stat();
+    auto end = worker_stat->next_downstream;
+    for (;;) {
+      auto i = worker_stat->next_downstream;
+      ++worker_stat->next_downstream;
+      worker_stat->next_downstream %= get_config()->downstream_addrs.size();
+
+      conn_.fd = util::create_nonblock_socket(
+          get_config()->downstream_addrs[i].addr.storage.ss_family);
+
+      if (conn_.fd == -1) {
+        auto error = errno;
+        DCLOG(WARN, this) << "socket() failed; errno=" << error;
+
+        connect_blocker->on_failure();
+
+        return SHRPX_ERR_NETWORK;
+      }
+
+      int rv;
+      rv = connect(conn_.fd, const_cast<sockaddr *>(
+                                 &get_config()->downstream_addrs[i].addr.sa),
+                   get_config()->downstream_addrs[i].addrlen);
+      if (rv != 0 && errno != EINPROGRESS) {
+        auto error = errno;
+        DCLOG(WARN, this) << "connect() failed; errno=" << error;
+
+        connect_blocker->on_failure();
+        close(conn_.fd);
+        conn_.fd = -1;
+
+        if (end == worker_stat->next_downstream) {
+          return SHRPX_ERR_NETWORK;
+        }
+
+        // Try again with the next downstream server
+        continue;
+      }
+
+      if (LOG_ENABLED(INFO)) {
+        DCLOG(INFO, this) << "Connecting to downstream server";
+      }
+
+      addr_idx_ = i;
+
+      ev_io_set(&conn_.wev, conn_.fd, EV_WRITE);
+      ev_io_set(&conn_.rev, conn_.fd, EV_READ);
+
+      conn_.wlimit.startw();
+
+      break;
+    }
+  }
+
+  downstream_ = downstream;
+
+  http_parser_init(&response_htp_, HTTP_RESPONSE);
+  response_htp_.data = downstream_;
+
+  ev_set_cb(&conn_.rev, readcb);
+
+  conn_.rt.repeat = get_config()->downstream_read_timeout;
+  ev_timer_again(conn_.loop, &conn_.rt);
+  // TODO we should have timeout for connection establishment
+  ev_timer_again(conn_.loop, &conn_.wt);
+
+  return 0;
+}
+
+int HttpDownstreamConnection::push_request_headers() {
+  const char *authority = nullptr, *host = nullptr;
+  if (!get_config()->no_host_rewrite && !get_config()->http2_proxy &&
+      !get_config()->client_proxy) {
+    if (!downstream_->get_request_http2_authority().empty()) {
+      authority = get_config()->downstream_addrs[addr_idx_].hostport.get();
+    }
+    if (downstream_->get_request_header(http2::HD_HOST)) {
+      host = get_config()->downstream_addrs[addr_idx_].hostport.get();
+    }
+  } else {
+    if (!downstream_->get_request_http2_authority().empty()) {
+      authority = downstream_->get_request_http2_authority().c_str();
+    }
+    auto h = downstream_->get_request_header(http2::HD_HOST);
+    if (h) {
+      host = h->value.c_str();
+    }
+  }
+
+  if (!authority && !host) {
+    // upstream is HTTP/1.0.  We use backend server's host
+    // nonetheless.
+    host = get_config()->downstream_addrs[addr_idx_].hostport.get();
+  }
+
+  if (authority) {
+    downstream_->set_request_downstream_host(authority);
+  } else {
+    downstream_->set_request_downstream_host(host);
+  }
+
+  downstream_->assemble_request_cookie();
+
+  // Assume that method and request path do not contain \r\n.
+  std::string hdrs = downstream_->get_request_method();
+  hdrs += " ";
+  if (downstream_->get_request_method() == "CONNECT") {
+    if (authority) {
+      hdrs += authority;
+    } else {
+      hdrs += downstream_->get_request_path();
+    }
+  } else if (get_config()->http2_proxy &&
+             !downstream_->get_request_http2_scheme().empty() && authority &&
+             (downstream_->get_request_path().c_str()[0] == '/' ||
+              downstream_->get_request_path() == "*")) {
+    // Construct absolute-form request target because we are going to
+    // send a request to a HTTP/1 proxy.
+    hdrs += downstream_->get_request_http2_scheme();
+    hdrs += "://";
+    hdrs += authority;
+
+    // Server-wide OPTIONS takes following form in proxy request:
+    //
+    // OPTIONS http://example.org HTTP/1.1
+    //
+    // Notice that no slash after authority. See
+    // http://tools.ietf.org/html/rfc7230#section-5.3.4
+    if (downstream_->get_request_path() != "*") {
+      hdrs += downstream_->get_request_path();
+    }
+  } else {
+    // No proxy case. get_request_path() may be absolute-form but we
+    // don't care.
+    hdrs += downstream_->get_request_path();
+  }
+  hdrs += " HTTP/1.1\r\nHost: ";
+  if (authority) {
+    hdrs += authority;
+  } else {
+    hdrs += host;
+  }
+  hdrs += "\r\n";
+
+  http2::build_http1_headers_from_headers(hdrs,
+                                          downstream_->get_request_headers());
+
+  if (!downstream_->get_assembled_request_cookie().empty()) {
+    hdrs += "Cookie: ";
+    hdrs += downstream_->get_assembled_request_cookie();
+    hdrs += "\r\n";
+  }
+
+  if (downstream_->get_request_method() != "CONNECT" &&
+      downstream_->get_request_http2_expect_body() &&
+      !downstream_->get_request_header(http2::HD_CONTENT_LENGTH)) {
+
+    downstream_->set_chunked_request(true);
+    hdrs += "Transfer-Encoding: chunked\r\n";
+  }
+
+  if (downstream_->get_request_connection_close()) {
+    hdrs += "Connection: close\r\n";
+  }
+  auto xff = downstream_->get_request_header(http2::HD_X_FORWARDED_FOR);
+  if (get_config()->add_x_forwarded_for) {
+    hdrs += "X-Forwarded-For: ";
+    if (xff && !get_config()->strip_incoming_x_forwarded_for) {
+      hdrs += (*xff).value;
+      hdrs += ", ";
+    }
+    hdrs += client_handler_->get_ipaddr();
+    hdrs += "\r\n";
+  } else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
+    hdrs += "X-Forwarded-For: ";
+    hdrs += (*xff).value;
+    hdrs += "\r\n";
+  }
+  if (!get_config()->http2_proxy && !get_config()->client_proxy &&
+      downstream_->get_request_method() != "CONNECT") {
+    hdrs += "X-Forwarded-Proto: ";
+    if (!downstream_->get_request_http2_scheme().empty()) {
+      hdrs += downstream_->get_request_http2_scheme();
+      hdrs += "\r\n";
+    } else if (client_handler_->get_ssl()) {
+      hdrs += "https\r\n";
+    } else {
+      hdrs += "http\r\n";
+    }
+  }
+  auto expect = downstream_->get_request_header(http2::HD_EXPECT);
+  if (expect && !util::strifind((*expect).value.c_str(), "100-continue")) {
+    hdrs += "Expect: ";
+    hdrs += (*expect).value;
+    hdrs += "\r\n";
+  }
+  auto via = downstream_->get_request_header(http2::HD_VIA);
+  if (get_config()->no_via) {
+    if (via) {
+      hdrs += "Via: ";
+      hdrs += (*via).value;
+      hdrs += "\r\n";
+    }
+  } else {
+    hdrs += "Via: ";
+    if (via) {
+      hdrs += (*via).value;
+      hdrs += ", ";
+    }
+    hdrs += http::create_via_header_value(downstream_->get_request_major(),
+                                          downstream_->get_request_minor());
+    hdrs += "\r\n";
+  }
+
+  hdrs += "\r\n";
+  if (LOG_ENABLED(INFO)) {
+    const char *hdrp;
+    std::string nhdrs;
+    if (worker_config->errorlog_tty) {
+      nhdrs = http::colorizeHeaders(hdrs.c_str());
+      hdrp = nhdrs.c_str();
+    } else {
+      hdrp = hdrs.c_str();
+    }
+    DCLOG(INFO, this) << "HTTP request headers. stream_id="
+                      << downstream_->get_stream_id() << "\n" << hdrp;
+  }
+  auto output = downstream_->get_request_buf();
+  output->append(hdrs.c_str(), hdrs.size());
+
+  signal_write();
+
+  return 0;
+}
+
+int HttpDownstreamConnection::push_upload_data_chunk(const uint8_t *data,
+                                                     size_t datalen) {
+  auto chunked = downstream_->get_chunked_request();
+  auto output = downstream_->get_request_buf();
+
+  if (chunked) {
+    auto chunk_size_hex = util::utox(datalen);
+    output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
+    output->append("\r\n");
+  }
+
+  output->append(data, datalen);
+
+  if (chunked) {
+    output->append("\r\n");
+  }
+
+  signal_write();
+
+  return 0;
+}
+
+int HttpDownstreamConnection::end_upload_data() {
+  if (!downstream_->get_chunked_request()) {
+    return 0;
+  }
+
+  auto output = downstream_->get_request_buf();
+  output->append("0\r\n\r\n");
+
+  signal_write();
+
+  return 0;
+}
+
+namespace {
+void idle_readcb(struct ev_loop *loop, ev_io *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, dconn) << "Idle connection EOF";
+  }
+  auto dconn_pool = dconn->get_dconn_pool();
+  dconn_pool->remove_downstream_connection(dconn);
+  // dconn was deleted
+}
+} // namespace
+
+namespace {
+void idle_timeoutcb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto conn = static_cast<Connection *>(w->data);
+  auto dconn = static_cast<HttpDownstreamConnection *>(conn->data);
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, dconn) << "Idle connection timeout";
+  }
+  auto dconn_pool = dconn->get_dconn_pool();
+  dconn_pool->remove_downstream_connection(dconn);
+  // dconn was deleted
+}
+} // namespace
+
+void HttpDownstreamConnection::detach_downstream(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream;
+  }
+  downstream_ = nullptr;
+  ioctrl_.force_resume_read();
+
+  conn_.rlimit.startw();
+  conn_.wlimit.stopw();
+
+  ev_set_cb(&conn_.rev, idle_readcb);
+
+  ev_timer_stop(conn_.loop, &conn_.wt);
+
+  conn_.rt.repeat = get_config()->downstream_idle_read_timeout;
+  ev_set_cb(&conn_.rt, idle_timeoutcb);
+  ev_timer_again(conn_.loop, &conn_.rt);
+}
+
+void HttpDownstreamConnection::pause_read(IOCtrlReason reason) {
+  ioctrl_.pause_read(reason);
+}
+
+int HttpDownstreamConnection::resume_read(IOCtrlReason reason,
+                                          size_t consumed) {
+  if (!downstream_->response_buf_full()) {
+    ioctrl_.resume_read(reason);
+  }
+
+  return 0;
+}
+
+void HttpDownstreamConnection::force_resume_read() {
+  ioctrl_.force_resume_read();
+}
+
+namespace {
+int htp_msg_begincb(http_parser *htp) {
+  auto downstream = static_cast<Downstream *>(htp->data);
+
+  if (downstream->get_response_state() != Downstream::INITIAL) {
+    return -1;
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(http_parser *htp) {
+  auto downstream = static_cast<Downstream *>(htp->data);
+  auto upstream = downstream->get_upstream();
+  int rv;
+
+  downstream->set_response_http_status(htp->status_code);
+  downstream->set_response_major(htp->http_major);
+  downstream->set_response_minor(htp->http_minor);
+
+  if (downstream->index_response_headers() != 0) {
+    downstream->set_response_state(Downstream::MSG_BAD_HEADER);
+    return -1;
+  }
+
+  if (downstream->get_non_final_response()) {
+    // For non-final response code, we just call
+    // on_downstream_header_complete() without changing response
+    // state.
+    rv = upstream->on_downstream_header_complete(downstream);
+
+    if (rv != 0) {
+      return -1;
+    }
+
+    return 0;
+  }
+
+  downstream->set_response_connection_close(!http_should_keep_alive(htp));
+  downstream->set_response_state(Downstream::HEADER_COMPLETE);
+  downstream->inspect_http1_response();
+  downstream->check_upgrade_fulfilled();
+  if (downstream->get_upgraded()) {
+    downstream->set_response_connection_close(true);
+  }
+  if (upstream->on_downstream_header_complete(downstream) != 0) {
+    return -1;
+  }
+
+  if (downstream->get_upgraded()) {
+    // Upgrade complete, read until EOF in both ends
+    if (upstream->resume_read(SHRPX_MSG_BLOCK, downstream, 0) != 0) {
+      return -1;
+    }
+    downstream->set_request_state(Downstream::HEADER_COMPLETE);
+    if (LOG_ENABLED(INFO)) {
+      LOG(INFO) << "HTTP upgrade success. stream_id="
+                << downstream->get_stream_id();
+    }
+  }
+
+  unsigned int status = downstream->get_response_http_status();
+  // Ignore the response body. HEAD response may contain
+  // Content-Length or Transfer-Encoding: chunked.  Some server send
+  // 304 status code with nonzero Content-Length, but without response
+  // body. See
+  // http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-20#section-3.3
+
+  // TODO It seems that the cases other than HEAD are handled by
+  // http-parser.  Need test.
+  return downstream->get_request_method() == "HEAD" ||
+                 (100 <= status && status <= 199) || status == 204 ||
+                 status == 304
+             ? 1
+             : 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
+  auto downstream = static_cast<Downstream *>(htp->data);
+  if (downstream->get_response_state() != Downstream::INITIAL) {
+    // ignore trailers
+    return 0;
+  }
+  if (downstream->get_response_header_key_prev()) {
+    downstream->append_last_response_header_key(data, len);
+  } else {
+    downstream->add_response_header(std::string(data, len), "");
+  }
+  if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
+    if (LOG_ENABLED(INFO)) {
+      DLOG(INFO, downstream) << "Too large header block size="
+                             << downstream->get_response_headers_sum();
+    }
+    return -1;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
+  auto downstream = static_cast<Downstream *>(htp->data);
+  if (downstream->get_response_state() != Downstream::INITIAL) {
+    // ignore trailers
+    return 0;
+  }
+  if (downstream->get_response_header_key_prev()) {
+    downstream->set_last_response_header_value(std::string(data, len));
+  } else {
+    downstream->append_last_response_header_value(data, len);
+  }
+  if (downstream->get_response_headers_sum() > Downstream::MAX_HEADERS_SUM) {
+    if (LOG_ENABLED(INFO)) {
+      DLOG(INFO, downstream) << "Too large header block size="
+                             << downstream->get_response_headers_sum();
+    }
+    return -1;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(http_parser *htp, const char *data, size_t len) {
+  auto downstream = static_cast<Downstream *>(htp->data);
+
+  downstream->add_response_bodylen(len);
+
+  return downstream->get_upstream()->on_downstream_body(
+      downstream, reinterpret_cast<const uint8_t *>(data), len, true);
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(http_parser *htp) {
+  auto downstream = static_cast<Downstream *>(htp->data);
+
+  if (downstream->get_non_final_response()) {
+    downstream->reset_response();
+
+    return 0;
+  }
+
+  downstream->set_response_state(Downstream::MSG_COMPLETE);
+  // Block reading another response message from (broken?)
+  // server. This callback is not called if the connection is
+  // tunneled.
+  downstream->pause_read(SHRPX_MSG_BLOCK);
+  return downstream->get_upstream()->on_downstream_body_complete(downstream);
+}
+} // namespace
+
+namespace {
+http_parser_settings htp_hooks = {
+    htp_msg_begincb,     // http_cb on_message_begin;
+    nullptr,             // http_data_cb on_url;
+    nullptr,             // http_data_cb on_status;
+    htp_hdr_keycb,       // http_data_cb on_header_field;
+    htp_hdr_valcb,       // http_data_cb on_header_value;
+    htp_hdrs_completecb, // http_cb      on_headers_complete;
+    htp_bodycb,          // http_data_cb on_body;
+    htp_msg_completecb   // http_cb      on_message_complete;
+};
+} // namespace
+
+int HttpDownstreamConnection::on_read() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+  std::array<uint8_t, 8192> buf;
+  int rv;
+
+  if (downstream_->get_upgraded()) {
+    // For upgraded connection, just pass data to the upstream.
+    for (;;) {
+      auto nread = conn_.read_clear(buf.data(), buf.size());
+
+      if (nread == 0) {
+        return 0;
+      }
+
+      if (nread < 0) {
+        return nread;
+      }
+
+      rv = downstream_->get_upstream()->on_downstream_body(
+          downstream_, buf.data(), nread, true);
+      if (rv != 0) {
+        return rv;
+      }
+
+      if (downstream_->response_buf_full()) {
+        downstream_->pause_read(SHRPX_NO_BUFFER);
+        return 0;
+      }
+    }
+  }
+
+  for (;;) {
+    auto nread = conn_.read_clear(buf.data(), buf.size());
+
+    if (nread == 0) {
+      return 0;
+    }
+
+    if (nread < 0) {
+      return nread;
+    }
+
+    auto nproc =
+        http_parser_execute(&response_htp_, &htp_hooks,
+                            reinterpret_cast<char *>(buf.data()), nread);
+
+    if (nproc != static_cast<size_t>(nread)) {
+      if (LOG_ENABLED(INFO)) {
+        DCLOG(INFO, this) << "nproc != nread";
+      }
+      return -1;
+    }
+
+    auto htperr = HTTP_PARSER_ERRNO(&response_htp_);
+
+    if (htperr != HPE_OK) {
+      if (LOG_ENABLED(INFO)) {
+        DCLOG(INFO, this) << "HTTP parser failure: "
+                          << "(" << http_errno_name(htperr) << ") "
+                          << http_errno_description(htperr);
+      }
+
+      return -1;
+    }
+
+    if (downstream_->response_buf_full()) {
+      downstream_->pause_read(SHRPX_NO_BUFFER);
+      return 0;
+    }
+  }
+}
+
+int HttpDownstreamConnection::on_write() {
+  ev_timer_again(conn_.loop, &conn_.rt);
+
+  auto upstream = downstream_->get_upstream();
+  auto input = downstream_->get_request_buf();
+
+  std::array<struct iovec, MAX_WR_IOVCNT> iov;
+
+  while (input->rleft() > 0) {
+    auto iovcnt = input->riovec(iov.data(), iov.size());
+
+    auto nwrite = conn_.writev_clear(iov.data(), iovcnt);
+
+    if (nwrite == 0) {
+      return 0;
+    }
+
+    if (nwrite < 0) {
+      return nwrite;
+    }
+
+    input->drain(nwrite);
+  }
+
+  conn_.wlimit.stopw();
+  ev_timer_stop(conn_.loop, &conn_.wt);
+
+  if (input->rleft() == 0) {
+    upstream->resume_read(SHRPX_NO_BUFFER, downstream_,
+                          downstream_->get_request_datalen());
+  }
+
+  return 0;
+}
+
+int HttpDownstreamConnection::on_connect() {
+  auto connect_blocker = client_handler_->get_http1_connect_blocker();
+
+  if (!util::check_socket_connected(conn_.fd)) {
+    conn_.wlimit.stopw();
+
+    if (LOG_ENABLED(INFO)) {
+      DLOG(INFO, this) << "downstream connect failed";
+    }
+    connect_blocker->on_failure();
+    return -1;
+  }
+
+  connect_blocker->on_success();
+
+  conn_.rlimit.startw();
+  ev_set_cb(&conn_.wev, writecb);
+
+  return 0;
+}
+
+void HttpDownstreamConnection::on_upstream_change(Upstream *upstream) {}
+
+void HttpDownstreamConnection::signal_write() { conn_.wlimit.startw(); }
+
+} // namespace shrpx
diff --git a/src/shrpx_http_downstream_connection.h b/src/shrpx_http_downstream_connection.h
new file mode 100644 (file)
index 0000000..2eec5de
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
+#define SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
+
+#include "shrpx.h"
+
+#include "http-parser/http_parser.h"
+
+#include "shrpx_downstream_connection.h"
+#include "shrpx_io_control.h"
+#include "shrpx_connection.h"
+
+namespace shrpx {
+
+class DownstreamConnectionPool;
+
+class HttpDownstreamConnection : public DownstreamConnection {
+public:
+  HttpDownstreamConnection(DownstreamConnectionPool *dconn_pool,
+                           struct ev_loop *loop);
+  virtual ~HttpDownstreamConnection();
+  virtual int attach_downstream(Downstream *downstream);
+  virtual void detach_downstream(Downstream *downstream);
+
+  virtual int push_request_headers();
+  virtual int push_upload_data_chunk(const uint8_t *data, size_t datalen);
+  virtual int end_upload_data();
+
+  virtual void pause_read(IOCtrlReason reason);
+  virtual int resume_read(IOCtrlReason reason, size_t consumed);
+  virtual void force_resume_read();
+
+  virtual int on_read();
+  virtual int on_write();
+
+  virtual void on_upstream_change(Upstream *upstream);
+  virtual int on_priority_change(int32_t pri) { return 0; }
+
+  int on_connect();
+  void signal_write();
+
+private:
+  Connection conn_;
+  IOControl ioctrl_;
+  http_parser response_htp_;
+  // index of get_config()->downstream_addrs this object is using
+  size_t addr_idx_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTP_DOWNSTREAM_CONNECTION_H
diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc
new file mode 100644 (file)
index 0000000..3043986
--- /dev/null
@@ -0,0 +1,862 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_https_upstream.h"
+
+#include <cassert>
+#include <set>
+#include <sstream>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_http.h"
+#include "shrpx_config.h"
+#include "shrpx_error.h"
+#include "shrpx_worker_config.h"
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+HttpsUpstream::HttpsUpstream(ClientHandler *handler)
+    : handler_(handler), current_header_length_(0),
+      ioctrl_(handler->get_rlimit()) {
+  http_parser_init(&htp_, HTTP_REQUEST);
+  htp_.data = this;
+}
+
+HttpsUpstream::~HttpsUpstream() {}
+
+void HttpsUpstream::reset_current_header_length() {
+  current_header_length_ = 0;
+}
+
+namespace {
+int htp_msg_begin(http_parser *htp) {
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "HTTP request started";
+  }
+  upstream->reset_current_header_length();
+  // TODO specify 0 as priority for now
+  upstream->attach_downstream(make_unique<Downstream>(upstream, 0, 0));
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_uricb(http_parser *htp, const char *data, size_t len) {
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  auto downstream = upstream->get_downstream();
+  downstream->append_request_path(data, len);
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_keycb(http_parser *htp, const char *data, size_t len) {
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  auto downstream = upstream->get_downstream();
+  if (downstream->get_request_state() != Downstream::INITIAL) {
+    // ignore trailers
+    return 0;
+  }
+  if (downstream->get_request_header_key_prev()) {
+    downstream->append_last_request_header_key(data, len);
+  } else {
+    downstream->add_request_header(std::string(data, len), "");
+  }
+  if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, upstream) << "Too large header block size="
+                           << downstream->get_request_headers_sum();
+    }
+    return -1;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdr_valcb(http_parser *htp, const char *data, size_t len) {
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  auto downstream = upstream->get_downstream();
+  if (downstream->get_request_state() != Downstream::INITIAL) {
+    // ignore trailers
+    return 0;
+  }
+  if (downstream->get_request_header_key_prev()) {
+    downstream->set_last_request_header_value(std::string(data, len));
+  } else {
+    downstream->append_last_request_header_value(data, len);
+  }
+  if (downstream->get_request_headers_sum() > Downstream::MAX_HEADERS_SUM) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, upstream) << "Too large header block size="
+                           << downstream->get_request_headers_sum();
+    }
+    return -1;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_hdrs_completecb(http_parser *htp) {
+  int rv;
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "HTTP request headers completed";
+  }
+  auto downstream = upstream->get_downstream();
+
+  downstream->set_request_method(
+      http_method_str((enum http_method)htp->method));
+  downstream->set_request_major(htp->http_major);
+  downstream->set_request_minor(htp->http_minor);
+
+  downstream->set_request_connection_close(!http_should_keep_alive(htp));
+
+  if (LOG_ENABLED(INFO)) {
+    std::stringstream ss;
+    ss << downstream->get_request_method() << " "
+       << downstream->get_request_path() << " "
+       << "HTTP/" << downstream->get_request_major() << "."
+       << downstream->get_request_minor() << "\n";
+    const auto &headers = downstream->get_request_headers();
+    for (size_t i = 0; i < headers.size(); ++i) {
+      ss << TTY_HTTP_HD << headers[i].name << TTY_RST << ": "
+         << headers[i].value << "\n";
+    }
+    ULOG(INFO, upstream) << "HTTP request headers\n" << ss.str();
+  }
+
+  if (downstream->index_request_headers() != 0) {
+    return -1;
+  }
+
+  if (downstream->get_request_major() == 1 &&
+      downstream->get_request_minor() == 1 &&
+      !downstream->get_request_header(http2::HD_HOST)) {
+    return -1;
+  }
+
+  downstream->inspect_http1_request();
+
+  if (get_config()->client_proxy &&
+      downstream->get_request_method() != "CONNECT") {
+    // Make sure that request path is an absolute URI.
+    http_parser_url u;
+    auto url = downstream->get_request_path().c_str();
+    memset(&u, 0, sizeof(u));
+    rv = http_parser_parse_url(url, downstream->get_request_path().size(), 0,
+                               &u);
+    if (rv != 0 || !(u.field_set & (1 << UF_SCHEMA))) {
+      // Expect to respond with 400 bad request
+      return -1;
+    }
+  }
+
+  rv = downstream->attach_downstream_connection(
+      upstream->get_client_handler()->get_downstream_connection());
+
+  if (rv != 0) {
+    downstream->set_request_state(Downstream::CONNECT_FAIL);
+
+    return -1;
+  }
+
+  rv = downstream->push_request_headers();
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  downstream->set_request_state(Downstream::HEADER_COMPLETE);
+
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_bodycb(http_parser *htp, const char *data, size_t len) {
+  int rv;
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  auto downstream = upstream->get_downstream();
+  rv = downstream->push_upload_data_chunk(
+      reinterpret_cast<const uint8_t *>(data), len);
+  if (rv != 0) {
+    return -1;
+  }
+  return 0;
+}
+} // namespace
+
+namespace {
+int htp_msg_completecb(http_parser *htp) {
+  int rv;
+  auto upstream = static_cast<HttpsUpstream *>(htp->data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "HTTP request completed";
+  }
+  auto downstream = upstream->get_downstream();
+  downstream->set_request_state(Downstream::MSG_COMPLETE);
+  rv = downstream->end_upload_data();
+  if (rv != 0) {
+    return -1;
+  }
+  // Stop further processing to complete this request
+  http_parser_pause(htp, 1);
+  return 0;
+}
+} // namespace
+
+namespace {
+http_parser_settings htp_hooks = {
+    htp_msg_begin,       // http_cb      on_message_begin;
+    htp_uricb,           // http_data_cb on_url;
+    nullptr,             // http_data_cb on_status;
+    htp_hdr_keycb,       // http_data_cb on_header_field;
+    htp_hdr_valcb,       // http_data_cb on_header_value;
+    htp_hdrs_completecb, // http_cb      on_headers_complete;
+    htp_bodycb,          // http_data_cb on_body;
+    htp_msg_completecb   // http_cb      on_message_complete;
+};
+} // namespace
+
+// on_read() does not consume all available data in input buffer if
+// one http request is fully received.
+int HttpsUpstream::on_read() {
+  auto rb = handler_->get_rb();
+  auto downstream = get_downstream();
+
+  if (rb->rleft() == 0) {
+    return 0;
+  }
+
+  // downstream can be nullptr here, because it is initialized in the
+  // callback chain called by http_parser_execute()
+  if (downstream && downstream->get_upgraded()) {
+
+    auto rv = downstream->push_upload_data_chunk(rb->pos, rb->rleft());
+
+    if (rv != 0) {
+      return -1;
+    }
+
+    rb->reset();
+
+    if (downstream->request_buf_full()) {
+      if (LOG_ENABLED(INFO)) {
+        ULOG(INFO, this) << "Downstream request buf is full";
+      }
+      pause_read(SHRPX_NO_BUFFER);
+
+      return 0;
+    }
+
+    return 0;
+  }
+
+  auto nread = http_parser_execute(
+      &htp_, &htp_hooks, reinterpret_cast<const char *>(rb->pos), rb->rleft());
+
+  rb->drain(nread);
+
+  // Well, actually header length + some body bytes
+  current_header_length_ += nread;
+
+  // Get downstream again because it may be initialized in http parser
+  // execution
+  downstream = get_downstream();
+
+  auto handler = get_client_handler();
+  auto htperr = HTTP_PARSER_ERRNO(&htp_);
+
+  if (htperr == HPE_PAUSED) {
+
+    assert(downstream);
+
+    if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
+      // Following paues_read is needed to avoid reading next data.
+      pause_read(SHRPX_MSG_BLOCK);
+      error_reply(503);
+      handler_->signal_write();
+      // Downstream gets deleted after response body is read.
+      return 0;
+    }
+
+    assert(downstream->get_request_state() == Downstream::MSG_COMPLETE);
+
+    if (downstream->get_downstream_connection() == nullptr) {
+      // Error response has already be sent
+      assert(downstream->get_response_state() == Downstream::MSG_COMPLETE);
+      delete_downstream();
+
+      return 0;
+    }
+
+    if (handler->get_http2_upgrade_allowed() &&
+        downstream->get_http2_upgrade_request()) {
+
+      if (handler->perform_http2_upgrade(this) != 0) {
+        return -1;
+      }
+
+      handler_->signal_write();
+
+      return 0;
+    }
+
+    pause_read(SHRPX_MSG_BLOCK);
+
+    return 0;
+  }
+
+  if (htperr != HPE_OK) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "HTTP parse failure: "
+                       << "(" << http_errno_name(htperr) << ") "
+                       << http_errno_description(htperr);
+    }
+
+    pause_read(SHRPX_MSG_BLOCK);
+
+    unsigned int status_code;
+
+    if (downstream &&
+        downstream->get_request_state() == Downstream::CONNECT_FAIL) {
+      status_code = 503;
+    } else {
+      status_code = 400;
+    }
+
+    error_reply(status_code);
+
+    handler_->signal_write();
+
+    return 0;
+  }
+
+  // downstream can be NULL here.
+  if (downstream && downstream->request_buf_full()) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "Downstream request buffer is full";
+    }
+
+    pause_read(SHRPX_NO_BUFFER);
+
+    return 0;
+  }
+
+  return 0;
+}
+
+int HttpsUpstream::on_write() {
+  auto downstream = get_downstream();
+  if (!downstream) {
+    return 0;
+  }
+  auto wb = handler_->get_wb();
+  if (wb->wleft() == 0) {
+    return 0;
+  }
+
+  auto dconn = downstream->get_downstream_connection();
+  auto output = downstream->get_response_buf();
+
+  if (output->rleft() == 0 && dconn &&
+      downstream->get_response_state() != Downstream::MSG_COMPLETE) {
+    if (downstream->resume_read(SHRPX_NO_BUFFER,
+                                downstream->get_response_datalen()) != 0) {
+      return -1;
+    }
+
+    if (downstream_read(dconn) != 0) {
+      return -1;
+    }
+  }
+
+  auto n = output->remove(wb->last, wb->wleft());
+  wb->write(n);
+
+  if (wb->rleft() > 0) {
+    return 0;
+  }
+
+  // We need to postpone detachment until all data are sent so that
+  // we can notify nghttp2 library all data consumed.
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    if (downstream->get_response_connection_close()) {
+      // Connection close
+      downstream->pop_downstream_connection();
+      // dconn was deleted
+    } else {
+      // Keep-alive
+      downstream->detach_downstream_connection();
+    }
+    // We need this if response ends before request.
+    if (downstream->get_request_state() == Downstream::MSG_COMPLETE) {
+      delete_downstream();
+      return resume_read(SHRPX_MSG_BLOCK, nullptr, 0);
+    }
+  }
+
+  return downstream->resume_read(SHRPX_NO_BUFFER,
+                                 downstream->get_response_datalen());
+}
+
+int HttpsUpstream::on_event() { return 0; }
+
+ClientHandler *HttpsUpstream::get_client_handler() const { return handler_; }
+
+void HttpsUpstream::pause_read(IOCtrlReason reason) {
+  ioctrl_.pause_read(reason);
+}
+
+int HttpsUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+                               size_t consumed) {
+  // downstream could be nullptr if reason is SHRPX_MSG_BLOCK.
+  if (downstream && downstream->request_buf_full()) {
+    return 0;
+  }
+  if (ioctrl_.resume_read(reason)) {
+    // Process remaining data in input buffer here because these bytes
+    // are not notified by readcb until new data arrive.
+    http_parser_pause(&htp_, 0);
+    return on_read();
+  }
+
+  return 0;
+}
+
+int HttpsUpstream::downstream_read(DownstreamConnection *dconn) {
+  auto downstream = dconn->get_downstream();
+  int rv;
+
+  rv = downstream->on_read();
+
+  if (downstream->get_response_state() == Downstream::MSG_RESET) {
+    return -1;
+  }
+
+  if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) {
+    error_reply(502);
+    downstream->pop_downstream_connection();
+    goto end;
+  }
+
+  // Detach downstream connection early so that it could be reused
+  // without hitting server's request timeout.
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE &&
+      !downstream->get_response_connection_close()) {
+    // Keep-alive
+    downstream->detach_downstream_connection();
+  }
+
+  if (rv == SHRPX_ERR_EOF) {
+    return downstream_eof(dconn);
+  }
+
+  if (rv < 0) {
+    return downstream_error(dconn, Downstream::EVENT_ERROR);
+  }
+
+end:
+  handler_->signal_write();
+
+  return 0;
+}
+
+int HttpsUpstream::downstream_write(DownstreamConnection *dconn) {
+  int rv;
+  rv = dconn->on_write();
+  if (rv == SHRPX_ERR_NETWORK) {
+    return downstream_error(dconn, Downstream::EVENT_ERROR);
+  }
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  return 0;
+}
+
+int HttpsUpstream::downstream_eof(DownstreamConnection *dconn) {
+  auto downstream = dconn->get_downstream();
+
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, dconn) << "EOF";
+  }
+
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    goto end;
+  }
+
+  if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+    // Server may indicate the end of the request by EOF
+    if (LOG_ENABLED(INFO)) {
+      DCLOG(INFO, dconn) << "The end of the response body was indicated by "
+                         << "EOF";
+    }
+    on_downstream_body_complete(downstream);
+    downstream->set_response_state(Downstream::MSG_COMPLETE);
+    downstream->pop_downstream_connection();
+    goto end;
+  }
+
+  if (downstream->get_response_state() == Downstream::INITIAL) {
+    // we did not send any response headers, so we can reply error
+    // message.
+    if (LOG_ENABLED(INFO)) {
+      DCLOG(INFO, dconn) << "Return error reply";
+    }
+    error_reply(502);
+    downstream->pop_downstream_connection();
+    goto end;
+  }
+
+  // Otherwise, we don't know how to recover from this situation. Just
+  // drop connection.
+  return -1;
+end:
+  handler_->signal_write();
+
+  return 0;
+}
+
+int HttpsUpstream::downstream_error(DownstreamConnection *dconn, int events) {
+  auto downstream = dconn->get_downstream();
+  if (LOG_ENABLED(INFO)) {
+    if (events & Downstream::EVENT_ERROR) {
+      DCLOG(INFO, dconn) << "Network error/general error";
+    } else {
+      DCLOG(INFO, dconn) << "Timeout";
+    }
+  }
+  if (downstream->get_response_state() != Downstream::INITIAL) {
+    return -1;
+  }
+
+  unsigned int status;
+  if (events & Downstream::EVENT_TIMEOUT) {
+    status = 504;
+  } else {
+    status = 502;
+  }
+  error_reply(status);
+
+  downstream->pop_downstream_connection();
+
+  handler_->signal_write();
+  return 0;
+}
+
+void HttpsUpstream::error_reply(unsigned int status_code) {
+  auto html = http::create_error_html(status_code);
+  auto downstream = get_downstream();
+
+  if (!downstream) {
+    attach_downstream(make_unique<Downstream>(this, 1, 1));
+    downstream = get_downstream();
+  }
+
+  downstream->set_response_http_status(status_code);
+  // we are going to close connection for both frontend and backend in
+  // error condition.  This is safest option.
+  downstream->set_response_connection_close(true);
+  handler_->set_should_close_after_write(true);
+
+  auto output = downstream->get_response_buf();
+
+  output->append("HTTP/1.1 ");
+  auto status_str = http2::get_status_string(status_code);
+  output->append(status_str.c_str(), status_str.size());
+  output->append("\r\nServer: ");
+  output->append(get_config()->server_name, strlen(get_config()->server_name));
+  output->append("\r\nContent-Length: ");
+  auto cl = util::utos(html.size());
+  output->append(cl.c_str(), cl.size());
+  output->append("\r\nContent-Type: text/html; "
+                 "charset=UTF-8\r\nConnection: close\r\n\r\n");
+  output->append(html.c_str(), html.size());
+
+  downstream->add_response_sent_bodylen(html.size());
+  downstream->set_response_state(Downstream::MSG_COMPLETE);
+}
+
+void HttpsUpstream::attach_downstream(std::unique_ptr<Downstream> downstream) {
+  assert(!downstream_);
+  downstream_ = std::move(downstream);
+}
+
+void HttpsUpstream::delete_downstream() {
+  if (downstream_ && downstream_->accesslog_ready()) {
+    handler_->write_accesslog(downstream_.get());
+  }
+
+  downstream_.reset();
+}
+
+Downstream *HttpsUpstream::get_downstream() const { return downstream_.get(); }
+
+std::unique_ptr<Downstream> HttpsUpstream::pop_downstream() {
+  return std::unique_ptr<Downstream>(downstream_.release());
+}
+
+int HttpsUpstream::on_downstream_header_complete(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    if (downstream->get_non_final_response()) {
+      DLOG(INFO, downstream) << "HTTP non-final response header";
+    } else {
+      DLOG(INFO, downstream) << "HTTP response header completed";
+    }
+  }
+
+  std::string hdrs = "HTTP/";
+  hdrs += util::utos(downstream->get_request_major());
+  hdrs += ".";
+  hdrs += util::utos(downstream->get_request_minor());
+  hdrs += " ";
+  hdrs += http2::get_status_string(downstream->get_response_http_status());
+  hdrs += "\r\n";
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy &&
+      !get_config()->no_location_rewrite) {
+    downstream->rewrite_location_response_header(
+        get_client_handler()->get_upstream_scheme());
+  }
+
+  http2::build_http1_headers_from_headers(hdrs,
+                                          downstream->get_response_headers());
+
+  auto output = downstream->get_response_buf();
+
+  if (downstream->get_non_final_response()) {
+    hdrs += "\r\n";
+
+    if (LOG_ENABLED(INFO)) {
+      log_response_headers(hdrs);
+    }
+
+    output->append(hdrs.c_str(), hdrs.size());
+
+    downstream->clear_response_headers();
+
+    return 0;
+  }
+
+  // after graceful shutdown commenced, add connection: close header
+  // field.
+  if (worker_config->graceful_shutdown) {
+    downstream->set_response_connection_close(true);
+  }
+
+  // We check downstream->get_response_connection_close() in case when
+  // the Content-Length is not available.
+  if (!downstream->get_request_connection_close() &&
+      !downstream->get_response_connection_close()) {
+    if (downstream->get_request_major() <= 0 ||
+        downstream->get_request_minor() <= 0) {
+      // We add this header for HTTP/1.0 or HTTP/0.9 clients
+      hdrs += "Connection: Keep-Alive\r\n";
+    }
+  } else if (!downstream->get_upgraded() ||
+             downstream->get_request_method() != "CONNECT") {
+    hdrs += "Connection: close\r\n";
+  }
+
+  if (!downstream->get_response_header(http2::HD_ALT_SVC)) {
+    // We won't change or alter alt-svc from backend for now
+    if (!get_config()->altsvcs.empty()) {
+      hdrs += "Alt-Svc: ";
+
+      for (auto &altsvc : get_config()->altsvcs) {
+        hdrs += util::percent_encode_token(altsvc.protocol_id);
+        hdrs += "=\"";
+        hdrs += util::quote_string(std::string(altsvc.host, altsvc.host_len));
+        hdrs += ":";
+        hdrs += util::utos(altsvc.port);
+        hdrs += "\", ";
+      }
+
+      hdrs[hdrs.size() - 2] = '\r';
+      hdrs[hdrs.size() - 1] = '\n';
+    }
+  }
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy) {
+    hdrs += "Server: ";
+    hdrs += get_config()->server_name;
+    hdrs += "\r\n";
+  } else {
+    auto server = downstream->get_response_header(http2::HD_SERVER);
+    if (server) {
+      hdrs += "Server: ";
+      hdrs += (*server).value;
+      hdrs += "\r\n";
+    }
+  }
+
+  auto via = downstream->get_response_header(http2::HD_VIA);
+  if (get_config()->no_via) {
+    if (via) {
+      hdrs += "Via: ";
+      hdrs += (*via).value;
+      hdrs += "\r\n";
+    }
+  } else {
+    hdrs += "Via: ";
+    if (via) {
+      hdrs += (*via).value;
+      hdrs += ", ";
+    }
+    hdrs += http::create_via_header_value(downstream->get_response_major(),
+                                          downstream->get_response_minor());
+    hdrs += "\r\n";
+  }
+
+  for (auto &p : get_config()->add_response_headers) {
+    hdrs += p.first;
+    hdrs += ": ";
+    hdrs += p.second;
+    hdrs += "\r\n";
+  }
+
+  hdrs += "\r\n";
+
+  if (LOG_ENABLED(INFO)) {
+    log_response_headers(hdrs);
+  }
+
+  output->append(hdrs.c_str(), hdrs.size());
+
+  return 0;
+}
+
+int HttpsUpstream::on_downstream_body(Downstream *downstream,
+                                      const uint8_t *data, size_t len,
+                                      bool flush) {
+  if (len == 0) {
+    return 0;
+  }
+  auto output = downstream->get_response_buf();
+  if (downstream->get_chunked_response()) {
+    auto chunk_size_hex = util::utox(len);
+    chunk_size_hex += "\r\n";
+
+    output->append(chunk_size_hex.c_str(), chunk_size_hex.size());
+  }
+  output->append(data, len);
+
+  downstream->add_response_sent_bodylen(len);
+
+  if (downstream->get_chunked_response()) {
+    output->append("\r\n");
+  }
+  return 0;
+}
+
+int HttpsUpstream::on_downstream_body_complete(Downstream *downstream) {
+  if (downstream->get_chunked_response()) {
+    auto output = downstream->get_response_buf();
+    output->append("0\r\n\r\n");
+  }
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, downstream) << "HTTP response completed";
+  }
+
+  if (!downstream->validate_response_bodylen()) {
+    downstream->set_response_connection_close(true);
+  }
+
+  if (downstream->get_request_connection_close() ||
+      downstream->get_response_connection_close()) {
+    auto handler = get_client_handler();
+    handler->set_should_close_after_write(true);
+  }
+  return 0;
+}
+
+int HttpsUpstream::on_downstream_abort_request(Downstream *downstream,
+                                               unsigned int status_code) {
+  error_reply(status_code);
+  handler_->signal_write();
+  return 0;
+}
+
+void HttpsUpstream::log_response_headers(const std::string &hdrs) const {
+  const char *hdrp;
+  std::string nhdrs;
+  if (worker_config->errorlog_tty) {
+    nhdrs = http::colorizeHeaders(hdrs.c_str());
+    hdrp = nhdrs.c_str();
+  } else {
+    hdrp = hdrs.c_str();
+  }
+  ULOG(INFO, this) << "HTTP response headers\n" << hdrp;
+}
+
+void HttpsUpstream::on_handler_delete() {
+  if (downstream_ && downstream_->accesslog_ready()) {
+    handler_->write_accesslog(downstream_.get());
+  }
+}
+
+int HttpsUpstream::on_downstream_reset(bool no_retry) {
+  int rv;
+
+  if ((downstream_->get_request_state() != Downstream::HEADER_COMPLETE &&
+       downstream_->get_request_state() != Downstream::MSG_COMPLETE) ||
+      downstream_->get_response_state() != Downstream::INITIAL) {
+    // Return error so that caller can delete handler
+    return -1;
+  }
+
+  downstream_->pop_downstream_connection();
+
+  downstream_->add_retry();
+
+  if (no_retry || downstream_->no_more_retry()) {
+    if (on_downstream_abort_request(downstream_.get(), 503) != 0) {
+      return -1;
+    }
+    return 0;
+  }
+
+  rv = downstream_->attach_downstream_connection(
+      handler_->get_downstream_connection());
+  if (rv != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+MemchunkPool *HttpsUpstream::get_mcpool() { return &mcpool_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h
new file mode 100644 (file)
index 0000000..820d462
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_HTTPS_UPSTREAM_H
+#define SHRPX_HTTPS_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "http-parser/http_parser.h"
+
+#include "shrpx_upstream.h"
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class ClientHandler;
+
+class HttpsUpstream : public Upstream {
+public:
+  HttpsUpstream(ClientHandler *handler);
+  virtual ~HttpsUpstream();
+  virtual int on_read();
+  virtual int on_write();
+  virtual int on_event();
+  virtual int on_downstream_abort_request(Downstream *downstream,
+                                          unsigned int status_code);
+  virtual ClientHandler *get_client_handler() const;
+
+  virtual int downstream_read(DownstreamConnection *dconn);
+  virtual int downstream_write(DownstreamConnection *dconn);
+  virtual int downstream_eof(DownstreamConnection *dconn);
+  virtual int downstream_error(DownstreamConnection *dconn, int events);
+
+  void attach_downstream(std::unique_ptr<Downstream> downstream);
+  void delete_downstream();
+  Downstream *get_downstream() const;
+  std::unique_ptr<Downstream> pop_downstream();
+  void error_reply(unsigned int status_code);
+
+  virtual void pause_read(IOCtrlReason reason);
+  virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+                          size_t consumed);
+
+  virtual int on_downstream_header_complete(Downstream *downstream);
+  virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+                                 size_t len, bool flush);
+  virtual int on_downstream_body_complete(Downstream *downstream);
+
+  virtual void on_handler_delete();
+  virtual int on_downstream_reset(bool no_retry);
+
+  virtual MemchunkPool *get_mcpool();
+
+  void reset_current_header_length();
+  void log_response_headers(const std::string &hdrs) const;
+
+private:
+  ClientHandler *handler_;
+  http_parser htp_;
+  size_t current_header_length_;
+  // must be put before downstream_
+  MemchunkPool mcpool_;
+  std::unique_ptr<Downstream> downstream_;
+  IOControl ioctrl_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_HTTPS_UPSTREAM_H
diff --git a/src/shrpx_io_control.cc b/src/shrpx_io_control.cc
new file mode 100644 (file)
index 0000000..f43a257
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_io_control.h"
+
+#include <algorithm>
+
+#include "shrpx_rate_limit.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+IOControl::IOControl(RateLimit *lim) : lim_(lim), rdbits_(0) {}
+
+IOControl::~IOControl() {}
+
+void IOControl::pause_read(IOCtrlReason reason) {
+  rdbits_ |= reason;
+  if (lim_) {
+    lim_->stopw();
+  }
+}
+
+bool IOControl::resume_read(IOCtrlReason reason) {
+  rdbits_ &= ~reason;
+  if (rdbits_ == 0) {
+    if (lim_) {
+      lim_->startw();
+    }
+    return true;
+  }
+
+  return false;
+}
+
+void IOControl::force_resume_read() {
+  rdbits_ = 0;
+  if (lim_) {
+    lim_->startw();
+  }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_io_control.h b/src/shrpx_io_control.h
new file mode 100644 (file)
index 0000000..9baf95a
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_IO_CONTROL_H
+#define SHRPX_IO_CONTROL_H
+
+#include "shrpx.h"
+
+#include <vector>
+
+#include <ev.h>
+
+#include "shrpx_rate_limit.h"
+
+namespace shrpx {
+
+enum IOCtrlReason { SHRPX_NO_BUFFER = 1 << 0, SHRPX_MSG_BLOCK = 1 << 1 };
+
+class IOControl {
+public:
+  IOControl(RateLimit *lim);
+  ~IOControl();
+  void pause_read(IOCtrlReason reason);
+  // Returns true if read operation is enabled after this call
+  bool resume_read(IOCtrlReason reason);
+  // Clear all pause flags and enable read
+  void force_resume_read();
+
+private:
+  RateLimit *lim_;
+  uint32_t rdbits_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_IO_CONTROL_H
diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc
new file mode 100644 (file)
index 0000000..2c4d8f7
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_log.h"
+
+#include <syslog.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <cerrno>
+#include <cstdio>
+#include <cstring>
+#include <ctime>
+#include <iostream>
+#include <iomanip>
+
+#include "shrpx_config.h"
+#include "shrpx_downstream.h"
+#include "shrpx_worker_config.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+const char *SEVERITY_STR[] = {"INFO", "NOTICE", "WARN", "ERROR", "FATAL"};
+} // namespace
+
+namespace {
+const char *SEVERITY_COLOR[] = {
+    "\033[1;32m", // INFO
+    "\033[1;36m", // NOTICE
+    "\033[1;33m", // WARN
+    "\033[1;31m", // ERROR
+    "\033[1;35m", // FATAL
+};
+} // namespace
+
+int Log::severity_thres_ = NOTICE;
+
+void Log::set_severity_level(int severity) { severity_thres_ = severity; }
+
+int Log::set_severity_level_by_name(const char *name) {
+  for (size_t i = 0, max = array_size(SEVERITY_STR); i < max; ++i) {
+    if (strcmp(SEVERITY_STR[i], name) == 0) {
+      severity_thres_ = i;
+      return 0;
+    }
+  }
+  return -1;
+}
+
+int severity_to_syslog_level(int severity) {
+  switch (severity) {
+  case (INFO):
+    return LOG_INFO;
+  case (NOTICE):
+    return LOG_NOTICE;
+  case (WARN):
+    return LOG_WARNING;
+  case (ERROR):
+    return LOG_ERR;
+  case (FATAL):
+    return LOG_CRIT;
+  default:
+    return -1;
+  }
+}
+
+Log::Log(int severity, const char *filename, int linenum)
+    : filename_(filename), severity_(severity), linenum_(linenum) {}
+
+Log::~Log() {
+  int rv;
+
+  if (!get_config()) {
+    return;
+  }
+
+  auto wconf = worker_config;
+
+  if (!log_enabled(severity_) ||
+      (wconf->errorlog_fd == -1 && !get_config()->errorlog_syslog)) {
+    return;
+  }
+
+  if (get_config()->errorlog_syslog) {
+    if (severity_ == NOTICE) {
+      syslog(severity_to_syslog_level(severity_), "[%s] %s",
+             SEVERITY_STR[severity_], stream_.str().c_str());
+    } else {
+      syslog(severity_to_syslog_level(severity_), "[%s] %s (%s:%d)",
+             SEVERITY_STR[severity_], stream_.str().c_str(), filename_,
+             linenum_);
+    }
+
+    return;
+  }
+
+  char buf[4096];
+  auto tty = wconf->errorlog_tty;
+
+  wconf->update_tstamp(std::chrono::system_clock::now());
+  auto &time_local = wconf->time_local_str;
+
+  if (severity_ == NOTICE) {
+    rv = snprintf(buf, sizeof(buf), "%s PID%d [%s%s%s] %s\n",
+                  time_local.c_str(), get_config()->pid,
+                  tty ? SEVERITY_COLOR[severity_] : "", SEVERITY_STR[severity_],
+                  tty ? "\033[0m" : "", stream_.str().c_str());
+  } else {
+    rv = snprintf(buf, sizeof(buf), "%s PID%d [%s%s%s] %s%s:%d%s %s\n",
+                  time_local.c_str(), get_config()->pid,
+                  tty ? SEVERITY_COLOR[severity_] : "", SEVERITY_STR[severity_],
+                  tty ? "\033[0m" : "", tty ? "\033[1;30m" : "", filename_,
+                  linenum_, tty ? "\033[0m" : "", stream_.str().c_str());
+  }
+
+  if (rv < 0) {
+    return;
+  }
+
+  auto nwrite = std::min(static_cast<size_t>(rv), sizeof(buf) - 1);
+
+  while (write(wconf->errorlog_fd, buf, nwrite) == -1 && errno == EINTR)
+    ;
+}
+
+namespace {
+template <typename OutputIterator>
+std::pair<OutputIterator, size_t> copy(const char *src, size_t avail,
+                                       OutputIterator oitr) {
+  auto nwrite = std::min(strlen(src), avail);
+  auto noitr = std::copy_n(src, nwrite, oitr);
+  return std::make_pair(noitr, avail - nwrite);
+}
+} // namespace
+
+void upstream_accesslog(const std::vector<LogFragment> &lfv, LogSpec *lgsp) {
+  auto wconf = worker_config;
+
+  if (wconf->accesslog_fd == -1 && !get_config()->accesslog_syslog) {
+    return;
+  }
+
+  char buf[4096];
+
+  auto downstream = lgsp->downstream;
+
+  auto p = buf;
+  auto avail = sizeof(buf) - 2;
+
+  wconf->update_tstamp(lgsp->time_now);
+  auto &time_local = wconf->time_local_str;
+  auto &time_iso8601 = wconf->time_iso8601_str;
+
+  for (auto &lf : lfv) {
+    switch (lf.type) {
+    case SHRPX_LOGF_LITERAL:
+      std::tie(p, avail) = copy(lf.value.get(), avail, p);
+      break;
+    case SHRPX_LOGF_REMOTE_ADDR:
+      std::tie(p, avail) = copy(lgsp->remote_addr, avail, p);
+      break;
+    case SHRPX_LOGF_TIME_LOCAL:
+      std::tie(p, avail) = copy(time_local.c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_TIME_ISO8601:
+      std::tie(p, avail) = copy(time_iso8601.c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_REQUEST:
+      std::tie(p, avail) = copy(lgsp->method, avail, p);
+      std::tie(p, avail) = copy(" ", avail, p);
+      std::tie(p, avail) = copy(lgsp->path, avail, p);
+      std::tie(p, avail) = copy(" HTTP/", avail, p);
+      std::tie(p, avail) = copy(util::utos(lgsp->major).c_str(), avail, p);
+      std::tie(p, avail) = copy(".", avail, p);
+      std::tie(p, avail) = copy(util::utos(lgsp->minor).c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_STATUS:
+      std::tie(p, avail) = copy(util::utos(lgsp->status).c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_BODY_BYTES_SENT:
+      std::tie(p, avail) =
+          copy(util::utos(lgsp->body_bytes_sent).c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_HTTP:
+      if (downstream) {
+        auto hd = downstream->get_request_header(lf.value.get());
+        if (hd) {
+          std::tie(p, avail) = copy((*hd).value.c_str(), avail, p);
+          break;
+        }
+      }
+
+      std::tie(p, avail) = copy("-", avail, p);
+
+      break;
+    case SHRPX_LOGF_REMOTE_PORT:
+      std::tie(p, avail) = copy(lgsp->remote_port, avail, p);
+      break;
+    case SHRPX_LOGF_SERVER_PORT:
+      std::tie(p, avail) =
+          copy(util::utos(lgsp->server_port).c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_REQUEST_TIME: {
+      auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+                   lgsp->request_end_time - lgsp->request_start_time).count();
+
+      auto frac = util::utos(t % 1000);
+      auto sec = util::utos(t / 1000);
+      if (frac.size() < 3) {
+        frac = std::string(3 - frac.size(), '0') + frac;
+      }
+      sec += ".";
+      sec += frac;
+
+      std::tie(p, avail) = copy(sec.c_str(), avail, p);
+    } break;
+    case SHRPX_LOGF_PID:
+      std::tie(p, avail) = copy(util::utos(lgsp->pid).c_str(), avail, p);
+      break;
+    case SHRPX_LOGF_ALPN:
+      std::tie(p, avail) = copy(lgsp->alpn, avail, p);
+      break;
+    case SHRPX_LOGF_NONE:
+      break;
+    default:
+      break;
+    }
+  }
+
+  *p = '\0';
+
+  if (get_config()->accesslog_syslog) {
+    syslog(LOG_INFO, "%s", buf);
+
+    return;
+  }
+
+  *p++ = '\n';
+
+  auto nwrite = p - buf;
+  while (write(wconf->accesslog_fd, buf, nwrite) == -1 && errno == EINTR)
+    ;
+}
+
+int reopen_log_files() {
+  int res = 0;
+
+  auto wconf = worker_config;
+
+  if (wconf->accesslog_fd != -1) {
+    close(wconf->accesslog_fd);
+    wconf->accesslog_fd = -1;
+  }
+
+  if (!get_config()->accesslog_syslog && get_config()->accesslog_file) {
+
+    wconf->accesslog_fd =
+        util::reopen_log_file(get_config()->accesslog_file.get());
+
+    if (wconf->accesslog_fd == -1) {
+      LOG(ERROR) << "Failed to open accesslog file "
+                 << get_config()->accesslog_file.get();
+      res = -1;
+    }
+  }
+
+  int new_errorlog_fd = -1;
+
+  if (!get_config()->errorlog_syslog && get_config()->errorlog_file) {
+
+    new_errorlog_fd = util::reopen_log_file(get_config()->errorlog_file.get());
+
+    if (new_errorlog_fd == -1) {
+      if (wconf->errorlog_fd != -1) {
+        LOG(ERROR) << "Failed to open errorlog file "
+                   << get_config()->errorlog_file.get();
+      } else {
+        std::cerr << "Failed to open errorlog file "
+                  << get_config()->errorlog_file.get() << std::endl;
+      }
+
+      res = -1;
+    }
+  }
+
+  if (wconf->errorlog_fd != -1) {
+    close(wconf->errorlog_fd);
+    wconf->errorlog_fd = -1;
+    wconf->errorlog_tty = false;
+  }
+
+  if (new_errorlog_fd != -1) {
+    wconf->errorlog_fd = new_errorlog_fd;
+    wconf->errorlog_tty = isatty(wconf->errorlog_fd);
+  }
+
+  return res;
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_log.h b/src/shrpx_log.h
new file mode 100644 (file)
index 0000000..e4da430
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_LOG_H
+#define SHRPX_LOG_H
+
+#include "shrpx.h"
+
+#include <sys/types.h>
+
+#include <sstream>
+#include <memory>
+#include <vector>
+#include <chrono>
+
+namespace shrpx {
+
+class Downstream;
+
+#define ENABLE_LOG 1
+
+#define LOG_ENABLED(SEVERITY) (ENABLE_LOG && Log::log_enabled(SEVERITY))
+
+#define LOG(SEVERITY) Log(SEVERITY, __FILE__, __LINE__)
+
+// Listener log
+#define LLOG(SEVERITY, LISTEN)                                                 \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[LISTEN:" << LISTEN << "] ")
+
+// Worker log
+#define WLOG(SEVERITY, WORKER)                                                 \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[WORKER:" << WORKER << "] ")
+
+// ClientHandler log
+#define CLOG(SEVERITY, CLIENT_HANDLER)                                         \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[CLIENT_HANDLER:" << CLIENT_HANDLER   \
+                                     << "] ")
+
+// Upstream log
+#define ULOG(SEVERITY, UPSTREAM)                                               \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[UPSTREAM:" << UPSTREAM << "] ")
+
+// Downstream log
+#define DLOG(SEVERITY, DOWNSTREAM)                                             \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[DOWNSTREAM:" << DOWNSTREAM << "] ")
+
+// Downstream connection log
+#define DCLOG(SEVERITY, DCONN)                                                 \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[DCONN:" << DCONN << "] ")
+
+// Downstream HTTP2 session log
+#define SSLOG(SEVERITY, HTTP2)                                                 \
+  (Log(SEVERITY, __FILE__, __LINE__) << "[DHTTP2:" << HTTP2 << "] ")
+
+enum SeverityLevel { INFO, NOTICE, WARN, ERROR, FATAL };
+
+class Log {
+public:
+  Log(int severity, const char *filename, int linenum);
+  ~Log();
+  template <typename Type> Log &operator<<(Type s) {
+    stream_ << s;
+    return *this;
+  }
+  static void set_severity_level(int severity);
+  static int set_severity_level_by_name(const char *name);
+  static bool log_enabled(int severity) { return severity >= severity_thres_; }
+
+private:
+  std::stringstream stream_;
+  const char *filename_;
+  int severity_;
+  int linenum_;
+  static int severity_thres_;
+};
+
+#define TTY_HTTP_HD (worker_config->errorlog_tty ? "\033[1;34m" : "")
+#define TTY_RST (worker_config->errorlog_tty ? "\033[0m" : "")
+
+enum LogFragmentType {
+  SHRPX_LOGF_NONE,
+  SHRPX_LOGF_LITERAL,
+  SHRPX_LOGF_REMOTE_ADDR,
+  SHRPX_LOGF_TIME_LOCAL,
+  SHRPX_LOGF_TIME_ISO8601,
+  SHRPX_LOGF_REQUEST,
+  SHRPX_LOGF_STATUS,
+  SHRPX_LOGF_BODY_BYTES_SENT,
+  SHRPX_LOGF_HTTP,
+  SHRPX_LOGF_REMOTE_PORT,
+  SHRPX_LOGF_SERVER_PORT,
+  SHRPX_LOGF_REQUEST_TIME,
+  SHRPX_LOGF_PID,
+  SHRPX_LOGF_ALPN,
+};
+
+struct LogFragment {
+  LogFragmentType type;
+  std::unique_ptr<char[]> value;
+};
+
+struct LogSpec {
+  Downstream *downstream;
+  const char *remote_addr;
+  const char *method;
+  const char *path;
+  const char *alpn;
+  std::chrono::system_clock::time_point time_now;
+  std::chrono::high_resolution_clock::time_point request_start_time;
+  std::chrono::high_resolution_clock::time_point request_end_time;
+  int major, minor;
+  unsigned int status;
+  int64_t body_bytes_sent;
+  const char *remote_port;
+  uint16_t server_port;
+  pid_t pid;
+};
+
+void upstream_accesslog(const std::vector<LogFragment> &lf, LogSpec *lgsp);
+
+int reopen_log_files();
+
+} // namespace shrpx
+
+#endif // SHRPX_LOG_H
diff --git a/src/shrpx_rate_limit.cc b/src/shrpx_rate_limit.cc
new file mode 100644 (file)
index 0000000..3f2ec95
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_rate_limit.h"
+
+#include <limits>
+
+namespace shrpx {
+
+namespace {
+void regencb(struct ev_loop *loop, ev_timer *w, int revents) {
+  auto r = static_cast<RateLimit *>(w->data);
+  r->regen();
+}
+} // namespace
+
+RateLimit::RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst)
+    : w_(w), loop_(loop), rate_(rate), burst_(burst), avail_(burst),
+      startw_req_(false) {
+  ev_timer_init(&t_, regencb, 0., 1.);
+  t_.data = this;
+  if (rate_ > 0) {
+    ev_timer_again(loop_, &t_);
+  }
+}
+
+RateLimit::~RateLimit() {
+  ev_timer_stop(loop_, &t_);
+}
+
+size_t RateLimit::avail() const {
+  if (rate_ == 0) {
+    return std::numeric_limits<ssize_t>::max();
+  }
+  return avail_;
+}
+
+void RateLimit::drain(size_t n) {
+  if (rate_ == 0) {
+    return;
+  }
+  n = std::min(avail_, n);
+  avail_ -= n;
+  if (avail_ == 0) {
+    ev_io_stop(loop_, w_);
+  }
+}
+
+void RateLimit::regen() {
+  if (rate_ == 0) {
+    return;
+  }
+  if (avail_ + rate_ > burst_) {
+    avail_ = burst_;
+  } else {
+    avail_ += rate_;
+  }
+
+  if (avail_ > 0 && startw_req_) {
+    ev_io_start(loop_, w_);
+  }
+}
+
+void RateLimit::startw() {
+  startw_req_ = true;
+  if (rate_ == 0 || avail_ > 0) {
+    ev_io_start(loop_, w_);
+    return;
+  }
+}
+
+void RateLimit::stopw() {
+  startw_req_ = false;
+  ev_io_stop(loop_, w_);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_rate_limit.h b/src/shrpx_rate_limit.h
new file mode 100644 (file)
index 0000000..db51df4
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_RATE_LIMIT_H
+#define SHRPX_RATE_LIMIT_H
+
+#include "shrpx.h"
+
+#include <ev.h>
+
+namespace shrpx {
+
+class RateLimit {
+public:
+  RateLimit(struct ev_loop *loop, ev_io *w, size_t rate, size_t burst);
+  ~RateLimit();
+  size_t avail() const;
+  void drain(size_t n);
+  void regen();
+  void startw();
+  void stopw();
+private:
+  ev_io *w_;
+  ev_timer t_;
+  struct ev_loop *loop_;
+  size_t rate_;
+  size_t burst_;
+  size_t avail_;
+  bool startw_req_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_RATE_LIMIT_H
diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc
new file mode 100644 (file)
index 0000000..6ca21c1
--- /dev/null
@@ -0,0 +1,1080 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_spdy_upstream.h"
+
+#include <netinet/tcp.h>
+#include <assert.h>
+#include <cerrno>
+#include <sstream>
+
+#include <nghttp2/nghttp2.h>
+
+#include "shrpx_client_handler.h"
+#include "shrpx_downstream.h"
+#include "shrpx_downstream_connection.h"
+#include "shrpx_config.h"
+#include "shrpx_http.h"
+#include "shrpx_worker_config.h"
+#include "http2.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+ssize_t send_callback(spdylay_session *session, const uint8_t *data, size_t len,
+                      int flags, void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  auto handler = upstream->get_client_handler();
+  auto wb = handler->get_wb();
+
+  if (wb->wleft() == 0) {
+    return SPDYLAY_ERR_WOULDBLOCK;
+  }
+
+  auto nread = wb->write(data, len);
+
+  return nread;
+}
+} // namespace
+
+namespace {
+ssize_t recv_callback(spdylay_session *session, uint8_t *buf, size_t len,
+                      int flags, void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  auto handler = upstream->get_client_handler();
+  auto rb = handler->get_rb();
+
+  if (rb->rleft() == 0) {
+    return SPDYLAY_ERR_WOULDBLOCK;
+  }
+
+  auto nread = std::min(rb->rleft(), len);
+
+  memcpy(buf, rb->pos, nread);
+  rb->drain(nread);
+
+  return nread;
+}
+} // namespace
+
+namespace {
+void on_stream_close_callback(spdylay_session *session, int32_t stream_id,
+                              spdylay_status_code status_code,
+                              void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Stream stream_id=" << stream_id
+                         << " is being closed";
+  }
+  auto downstream = upstream->find_downstream(stream_id);
+  if (!downstream) {
+    return;
+  }
+
+  upstream->consume(stream_id, downstream->get_request_datalen());
+
+  downstream->reset_request_datalen();
+
+  if (downstream->get_request_state() == Downstream::CONNECT_FAIL) {
+    upstream->remove_downstream(downstream);
+    // downstrea was deleted
+
+    return;
+  }
+
+  downstream->set_request_state(Downstream::STREAM_CLOSED);
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    // At this point, downstream response was read
+    if (!downstream->get_upgraded() &&
+        !downstream->get_response_connection_close()) {
+      // Keep-alive
+      downstream->detach_downstream_connection();
+    }
+    upstream->remove_downstream(downstream);
+    // downstrea was deleted
+
+    return;
+  }
+
+  // At this point, downstream read may be paused.
+
+  // If shrpx_downstream::push_request_headers() failed, the
+  // error is handled here.
+  upstream->remove_downstream(downstream);
+  // downstrea was deleted
+
+  // How to test this case? Request sufficient large download
+  // and make client send RST_STREAM after it gets first DATA
+  // frame chunk.
+}
+} // namespace
+
+namespace {
+void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
+                           spdylay_frame *frame, void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  switch (type) {
+  case SPDYLAY_SYN_STREAM: {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, upstream) << "Received upstream SYN_STREAM stream_id="
+                           << frame->syn_stream.stream_id;
+    }
+
+    auto downstream = upstream->add_pending_downstream(
+        frame->syn_stream.stream_id, frame->syn_stream.pri);
+
+    downstream->reset_upstream_rtimer();
+
+    auto nv = frame->syn_stream.nv;
+
+    if (LOG_ENABLED(INFO)) {
+      std::stringstream ss;
+      for (size_t i = 0; nv[i]; i += 2) {
+        ss << TTY_HTTP_HD << nv[i] << TTY_RST << ": " << nv[i + 1] << "\n";
+      }
+      ULOG(INFO, upstream) << "HTTP request headers. stream_id="
+                           << downstream->get_stream_id() << "\n" << ss.str();
+    }
+
+    for (size_t i = 0; nv[i]; i += 2) {
+      downstream->add_request_header(nv[i], nv[i + 1]);
+    }
+
+    if (downstream->index_request_headers() != 0) {
+      if (upstream->error_reply(downstream, 400) != 0) {
+        ULOG(FATAL, upstream) << "error_reply failed";
+      }
+      return;
+    }
+
+    auto path = downstream->get_request_header(http2::HD__PATH);
+    auto scheme = downstream->get_request_header(http2::HD__SCHEME);
+    auto host = downstream->get_request_header(http2::HD__HOST);
+    auto method = downstream->get_request_header(http2::HD__METHOD);
+
+    bool is_connect = method && "CONNECT" == method->value;
+    if (!path || !host || !method || !http2::non_empty_value(host) ||
+        !http2::non_empty_value(path) || !http2::non_empty_value(method) ||
+        (!is_connect && (!scheme || !http2::non_empty_value(scheme)))) {
+      upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+      return;
+    }
+
+    downstream->set_request_method(method->value);
+    if (is_connect) {
+      downstream->set_request_http2_authority(path->value);
+    } else {
+      downstream->set_request_http2_scheme(scheme->value);
+      downstream->set_request_http2_authority(host->value);
+      downstream->set_request_path(path->value);
+    }
+
+    if (!(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN)) {
+      downstream->set_request_http2_expect_body(true);
+    }
+
+    downstream->inspect_http2_request();
+
+    downstream->set_request_state(Downstream::HEADER_COMPLETE);
+    if (frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) {
+      if (!downstream->validate_request_bodylen()) {
+        upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
+        return;
+      }
+
+      downstream->disable_upstream_rtimer();
+      downstream->set_request_state(Downstream::MSG_COMPLETE);
+    }
+
+    upstream->start_downstream(downstream);
+
+    break;
+  }
+  default:
+    break;
+  }
+}
+} // namespace
+
+void SpdyUpstream::start_downstream(Downstream *downstream) {
+  auto next_downstream =
+      downstream_queue_.pop_pending(downstream->get_stream_id());
+  assert(next_downstream);
+
+  if (downstream_queue_.can_activate(
+          downstream->get_request_http2_authority())) {
+    initiate_downstream(std::move(next_downstream));
+    return;
+  }
+
+  downstream_queue_.add_blocked(std::move(next_downstream));
+}
+
+void SpdyUpstream::initiate_downstream(std::unique_ptr<Downstream> downstream) {
+  int rv = downstream->attach_downstream_connection(
+      handler_->get_downstream_connection());
+  if (rv != 0) {
+    // If downstream connection fails, issue RST_STREAM.
+    rst_stream(downstream.get(), SPDYLAY_INTERNAL_ERROR);
+    downstream->set_request_state(Downstream::CONNECT_FAIL);
+
+    downstream_queue_.add_failure(std::move(downstream));
+
+    return;
+  }
+  rv = downstream->push_request_headers();
+  if (rv != 0) {
+    rst_stream(downstream.get(), SPDYLAY_INTERNAL_ERROR);
+
+    downstream_queue_.add_failure(std::move(downstream));
+
+    return;
+  }
+
+  downstream_queue_.add_active(std::move(downstream));
+}
+
+namespace {
+void on_data_chunk_recv_callback(spdylay_session *session, uint8_t flags,
+                                 int32_t stream_id, const uint8_t *data,
+                                 size_t len, void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  auto downstream = upstream->find_downstream(stream_id);
+
+  if (!downstream) {
+    upstream->consume(stream_id, len);
+
+    return;
+  }
+
+  downstream->reset_upstream_rtimer();
+
+  if (downstream->push_upload_data_chunk(data, len) != 0) {
+    upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+
+    upstream->consume(stream_id, len);
+
+    return;
+  }
+
+  if (!upstream->get_flow_control()) {
+    return;
+  }
+
+  // If connection-level window control is not enabled (e.g,
+  // spdy/3), spdylay_session_get_recv_data_length() is always
+  // returns 0.
+  if (spdylay_session_get_recv_data_length(session) >
+      std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
+               1 << get_config()->http2_upstream_connection_window_bits)) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, upstream)
+          << "Flow control error on connection: "
+          << "recv_window_size="
+          << spdylay_session_get_recv_data_length(session) << ", window_size="
+          << (1 << get_config()->http2_upstream_connection_window_bits);
+    }
+    spdylay_session_fail_session(session, SPDYLAY_GOAWAY_PROTOCOL_ERROR);
+    return;
+  }
+  if (spdylay_session_get_stream_recv_data_length(session, stream_id) >
+      std::max(SPDYLAY_INITIAL_WINDOW_SIZE,
+               1 << get_config()->http2_upstream_window_bits)) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, upstream) << "Flow control error: recv_window_size="
+                           << spdylay_session_get_stream_recv_data_length(
+                                  session, stream_id)
+                           << ", initial_window_size="
+                           << (1 << get_config()->http2_upstream_window_bits);
+    }
+    upstream->rst_stream(downstream, SPDYLAY_FLOW_CONTROL_ERROR);
+    return;
+  }
+}
+} // namespace
+
+namespace {
+void on_data_recv_callback(spdylay_session *session, uint8_t flags,
+                           int32_t stream_id, int32_t length, void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  auto downstream = upstream->find_downstream(stream_id);
+  if (downstream && (flags & SPDYLAY_DATA_FLAG_FIN)) {
+    if (!downstream->validate_request_bodylen()) {
+      upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
+      return;
+    }
+
+    downstream->disable_upstream_rtimer();
+    downstream->end_upload_data();
+    downstream->set_request_state(Downstream::MSG_COMPLETE);
+  }
+}
+} // namespace
+
+namespace {
+void on_ctrl_not_send_callback(spdylay_session *session,
+                               spdylay_frame_type type, spdylay_frame *frame,
+                               int error_code, void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Failed to send control frame type=" << type
+                         << ", error_code=" << error_code << ":"
+                         << spdylay_strerror(error_code);
+  }
+  if (type == SPDYLAY_SYN_REPLY && error_code != SPDYLAY_ERR_STREAM_CLOSED &&
+      error_code != SPDYLAY_ERR_STREAM_CLOSING) {
+    // To avoid stream hanging around, issue RST_STREAM.
+    auto stream_id = frame->syn_reply.stream_id;
+    auto downstream = upstream->find_downstream(stream_id);
+    if (downstream) {
+      upstream->rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+    }
+  }
+}
+} // namespace
+
+namespace {
+void on_ctrl_recv_parse_error_callback(spdylay_session *session,
+                                       spdylay_frame_type type,
+                                       const uint8_t *head, size_t headlen,
+                                       const uint8_t *payload,
+                                       size_t payloadlen, int error_code,
+                                       void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Failed to parse received control frame. type="
+                         << type << ", error_code=" << error_code << ":"
+                         << spdylay_strerror(error_code);
+  }
+}
+} // namespace
+
+namespace {
+void on_unknown_ctrl_recv_callback(spdylay_session *session,
+                                   const uint8_t *head, size_t headlen,
+                                   const uint8_t *payload, size_t payloadlen,
+                                   void *user_data) {
+  auto upstream = static_cast<SpdyUpstream *>(user_data);
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, upstream) << "Received unknown control frame.";
+  }
+}
+} // namespace
+
+namespace {
+// Infer upstream RST_STREAM status code from downstream HTTP/2
+// error code.
+uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) {
+  // Only propagate *_REFUSED_STREAM so that upstream client can
+  // resend request.
+  if (downstream_error_code == NGHTTP2_REFUSED_STREAM) {
+    return SPDYLAY_REFUSED_STREAM;
+  } else {
+    return SPDYLAY_INTERNAL_ERROR;
+  }
+}
+} // namespace
+
+SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
+    : downstream_queue_(
+          get_config()->http2_proxy
+              ? get_config()->downstream_connections_per_host
+              : get_config()->downstream_proto == PROTO_HTTP
+                    ? get_config()->downstream_connections_per_frontend
+                    : 0,
+          !get_config()->http2_proxy),
+      handler_(handler), session_(nullptr) {
+  spdylay_session_callbacks callbacks;
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = send_callback;
+  callbacks.recv_callback = recv_callback;
+  callbacks.on_stream_close_callback = on_stream_close_callback;
+  callbacks.on_ctrl_recv_callback = on_ctrl_recv_callback;
+  callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+  callbacks.on_data_recv_callback = on_data_recv_callback;
+  callbacks.on_ctrl_not_send_callback = on_ctrl_not_send_callback;
+  callbacks.on_ctrl_recv_parse_error_callback =
+      on_ctrl_recv_parse_error_callback;
+  callbacks.on_unknown_ctrl_recv_callback = on_unknown_ctrl_recv_callback;
+
+  int rv;
+  rv = spdylay_session_server_new(&session_, version, &callbacks, this);
+  assert(rv == 0);
+
+  if (version >= SPDYLAY_PROTO_SPDY3) {
+    int val = 1;
+    flow_control_ = true;
+    initial_window_size_ = 1 << get_config()->http2_upstream_window_bits;
+    rv = spdylay_session_set_option(
+        session_, SPDYLAY_OPT_NO_AUTO_WINDOW_UPDATE2, &val, sizeof(val));
+    assert(rv == 0);
+  } else {
+    flow_control_ = false;
+    initial_window_size_ = 0;
+  }
+  // TODO Maybe call from outside?
+  std::array<spdylay_settings_entry, 2> entry;
+  entry[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS;
+  entry[0].value = get_config()->http2_max_concurrent_streams;
+  entry[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
+
+  entry[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE;
+  entry[1].value = initial_window_size_;
+  entry[1].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE;
+
+  rv = spdylay_submit_settings(session_, SPDYLAY_FLAG_SETTINGS_NONE,
+                               entry.data(), entry.size());
+  assert(rv == 0);
+
+  if (version >= SPDYLAY_PROTO_SPDY3_1 &&
+      get_config()->http2_upstream_connection_window_bits > 16) {
+    int32_t delta = (1 << get_config()->http2_upstream_connection_window_bits) -
+                    SPDYLAY_INITIAL_WINDOW_SIZE;
+    rv = spdylay_submit_window_update(session_, 0, delta);
+    assert(rv == 0);
+  }
+
+  handler_->reset_upstream_read_timeout(
+      get_config()->http2_upstream_read_timeout);
+
+  handler_->signal_write();
+}
+
+SpdyUpstream::~SpdyUpstream() { spdylay_session_del(session_); }
+
+int SpdyUpstream::on_read() {
+  int rv = 0;
+
+  rv = spdylay_session_recv(session_);
+  if (rv < 0) {
+    if (rv != SPDYLAY_ERR_EOF) {
+      ULOG(ERROR, this) << "spdylay_session_recv() returned error: "
+                        << spdylay_strerror(rv);
+    }
+    return rv;
+  }
+
+  handler_->signal_write();
+
+  return 0;
+}
+
+// After this function call, downstream may be deleted.
+int SpdyUpstream::on_write() {
+  int rv = 0;
+
+  rv = spdylay_session_send(session_);
+  if (rv != 0) {
+    ULOG(ERROR, this) << "spdylay_session_send() returned error: "
+                      << spdylay_strerror(rv);
+    return rv;
+  }
+
+  if (spdylay_session_want_read(session_) == 0 &&
+      spdylay_session_want_write(session_) == 0 &&
+      handler_->get_wb()->rleft() == 0) {
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "No more read/write for this SPDY session";
+    }
+    return -1;
+  }
+  return 0;
+}
+
+ClientHandler *SpdyUpstream::get_client_handler() const { return handler_; }
+
+int SpdyUpstream::downstream_read(DownstreamConnection *dconn) {
+  auto downstream = dconn->get_downstream();
+
+  if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
+    // If upstream SPDY stream was closed, we just close downstream,
+    // because there is no consumer now. Downstream connection is also
+    // closed in this case.
+    remove_downstream(downstream);
+    // downstrea was deleted
+
+    return 0;
+  }
+
+  if (downstream->get_response_state() == Downstream::MSG_RESET) {
+    // The downstream stream was reset (canceled). In this case,
+    // RST_STREAM to the upstream and delete downstream connection
+    // here. Deleting downstream will be taken place at
+    // on_stream_close_callback.
+    rst_stream(downstream,
+               infer_upstream_rst_stream_status_code(
+                   downstream->get_response_rst_stream_error_code()));
+    downstream->pop_downstream_connection();
+    dconn = nullptr;
+  } else if (downstream->get_response_state() == Downstream::MSG_BAD_HEADER) {
+    if (error_reply(downstream, 502) != 0) {
+      return -1;
+    }
+    downstream->pop_downstream_connection();
+    // dconn was deleted
+    dconn = nullptr;
+  } else {
+    auto rv = downstream->on_read();
+    if (rv == SHRPX_ERR_EOF) {
+      return downstream_eof(dconn);
+    }
+    if (rv != 0) {
+      if (rv != SHRPX_ERR_NETWORK) {
+        if (LOG_ENABLED(INFO)) {
+          DCLOG(INFO, dconn) << "HTTP parser failure";
+        }
+      }
+      return downstream_error(dconn, Downstream::EVENT_ERROR);
+    }
+    // Detach downstream connection early so that it could be reused
+    // without hitting server's request timeout.
+    if (downstream->get_response_state() == Downstream::MSG_COMPLETE &&
+        !downstream->get_response_connection_close()) {
+      // Keep-alive
+      downstream->detach_downstream_connection();
+    }
+  }
+
+  handler_->signal_write();
+  // At this point, downstream may be deleted.
+
+  return 0;
+}
+
+int SpdyUpstream::downstream_write(DownstreamConnection *dconn) {
+  int rv;
+  rv = dconn->on_write();
+  if (rv == SHRPX_ERR_NETWORK) {
+    return downstream_error(dconn, Downstream::EVENT_ERROR);
+  }
+  if (rv != 0) {
+    return -1;
+  }
+  return 0;
+}
+
+int SpdyUpstream::downstream_eof(DownstreamConnection *dconn) {
+  auto downstream = dconn->get_downstream();
+
+  if (LOG_ENABLED(INFO)) {
+    DCLOG(INFO, dconn) << "EOF. stream_id=" << downstream->get_stream_id();
+  }
+  if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
+    // If stream was closed already, we don't need to send reply at
+    // the first place. We can delete downstream.
+    remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  // Delete downstream connection. If we don't delete it here, it will
+  // be pooled in on_stream_close_callback.
+  downstream->pop_downstream_connection();
+  // dconn was deleted
+  dconn = nullptr;
+  // downstream wil be deleted in on_stream_close_callback.
+  if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+    // Server may indicate the end of the request by EOF
+    if (LOG_ENABLED(INFO)) {
+      ULOG(INFO, this) << "Downstream body was ended by EOF";
+    }
+    downstream->set_response_state(Downstream::MSG_COMPLETE);
+
+    // For tunneled connection, MSG_COMPLETE signals
+    // downstream_data_read_callback to send RST_STREAM after pending
+    // response body is sent. This is needed to ensure that RST_STREAM
+    // is sent after all pending data are sent.
+    on_downstream_body_complete(downstream);
+  } else if (downstream->get_response_state() != Downstream::MSG_COMPLETE) {
+    // If stream was not closed, then we set MSG_COMPLETE and let
+    // on_stream_close_callback delete downstream.
+    if (error_reply(downstream, 502) != 0) {
+      return -1;
+    }
+  }
+  handler_->signal_write();
+  // At this point, downstream may be deleted.
+  return 0;
+}
+
+int SpdyUpstream::downstream_error(DownstreamConnection *dconn, int events) {
+  auto downstream = dconn->get_downstream();
+
+  if (LOG_ENABLED(INFO)) {
+    if (events & Downstream::EVENT_ERROR) {
+      DCLOG(INFO, dconn) << "Downstream network/general error";
+    } else {
+      DCLOG(INFO, dconn) << "Timeout";
+    }
+    if (downstream->get_upgraded()) {
+      DCLOG(INFO, dconn) << "Note: this is tunnel connection";
+    }
+  }
+
+  if (downstream->get_request_state() == Downstream::STREAM_CLOSED) {
+    remove_downstream(downstream);
+    // downstream was deleted
+
+    return 0;
+  }
+
+  // Delete downstream connection. If we don't delete it here, it will
+  // be pooled in on_stream_close_callback.
+  downstream->pop_downstream_connection();
+  // dconn was deleted
+  dconn = nullptr;
+
+  if (downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    // For SSL tunneling, we issue RST_STREAM. For other types of
+    // stream, we don't have to do anything since response was
+    // complete.
+    if (downstream->get_upgraded()) {
+      // We want "NO_ERROR" error code but SPDY does not have such
+      // code for RST_STREAM.
+      rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+    }
+  } else {
+    if (downstream->get_response_state() == Downstream::HEADER_COMPLETE) {
+      if (downstream->get_upgraded()) {
+        on_downstream_body_complete(downstream);
+      } else {
+        rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+      }
+    } else {
+      unsigned int status;
+      if (events & Downstream::EVENT_TIMEOUT) {
+        status = 504;
+      } else {
+        status = 502;
+      }
+      if (error_reply(downstream, status) != 0) {
+        return -1;
+      }
+    }
+    downstream->set_response_state(Downstream::MSG_COMPLETE);
+  }
+  handler_->signal_write();
+  // At this point, downstream may be deleted.
+  return 0;
+}
+
+int SpdyUpstream::rst_stream(Downstream *downstream, int status_code) {
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, this) << "RST_STREAM stream_id=" << downstream->get_stream_id();
+  }
+  int rv;
+  rv = spdylay_submit_rst_stream(session_, downstream->get_stream_id(),
+                                 status_code);
+  if (rv < SPDYLAY_ERR_FATAL) {
+    ULOG(FATAL, this) << "spdylay_submit_rst_stream() failed: "
+                      << spdylay_strerror(rv);
+    DIE();
+  }
+  return 0;
+}
+
+namespace {
+ssize_t spdy_data_read_callback(spdylay_session *session, int32_t stream_id,
+                                uint8_t *buf, size_t length, int *eof,
+                                spdylay_data_source *source, void *user_data) {
+  auto downstream = static_cast<Downstream *>(source->ptr);
+  auto upstream = static_cast<SpdyUpstream *>(downstream->get_upstream());
+  auto body = downstream->get_response_buf();
+  assert(body);
+
+  auto dconn = downstream->get_downstream_connection();
+
+  if (body->rleft() == 0 && dconn &&
+      downstream->get_response_state() != Downstream::MSG_COMPLETE) {
+    // Try to read more if buffer is empty.  This will help small
+    // buffer and make priority handling a bit better.
+    if (upstream->downstream_read(dconn) != 0) {
+      return SPDYLAY_ERR_CALLBACK_FAILURE;
+    }
+  }
+
+  auto nread = body->remove(buf, length);
+  auto body_empty = body->rleft() == 0;
+
+  if (nread == 0 &&
+      downstream->get_response_state() == Downstream::MSG_COMPLETE) {
+    if (!downstream->get_upgraded()) {
+      *eof = 1;
+    } else {
+      // For tunneling, issue RST_STREAM to finish the stream.
+      if (LOG_ENABLED(INFO)) {
+        ULOG(INFO, upstream)
+            << "RST_STREAM to tunneled stream stream_id=" << stream_id;
+      }
+      upstream->rst_stream(
+          downstream, infer_upstream_rst_stream_status_code(
+                          downstream->get_response_rst_stream_error_code()));
+    }
+  }
+
+  if (body_empty) {
+    downstream->disable_upstream_wtimer();
+  } else {
+    downstream->reset_upstream_wtimer();
+  }
+
+  if (nread > 0 && downstream->resume_read(SHRPX_NO_BUFFER, nread) != 0) {
+    return SPDYLAY_ERR_CALLBACK_FAILURE;
+  }
+
+  if (nread == 0 && *eof != 1) {
+    return SPDYLAY_ERR_DEFERRED;
+  }
+
+  if (nread > 0) {
+    downstream->add_response_sent_bodylen(nread);
+  }
+
+  return nread;
+}
+} // namespace
+
+int SpdyUpstream::error_reply(Downstream *downstream,
+                              unsigned int status_code) {
+  int rv;
+  auto html = http::create_error_html(status_code);
+  downstream->set_response_http_status(status_code);
+  auto body = downstream->get_response_buf();
+  body->append(html.c_str(), html.size());
+  downstream->set_response_state(Downstream::MSG_COMPLETE);
+
+  spdylay_data_provider data_prd;
+  data_prd.source.ptr = downstream;
+  data_prd.read_callback = spdy_data_read_callback;
+
+  std::string content_length = util::utos(html.size());
+  std::string status_string = http2::get_status_string(status_code);
+  const char *nv[] = {":status",        status_string.c_str(),
+                      ":version",       "http/1.1",
+                      "content-type",   "text/html; charset=UTF-8",
+                      "server",         get_config()->server_name,
+                      "content-length", content_length.c_str(),
+                      nullptr};
+
+  rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv,
+                               &data_prd);
+  if (rv < SPDYLAY_ERR_FATAL) {
+    ULOG(FATAL, this) << "spdylay_submit_response() failed: "
+                      << spdylay_strerror(rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+Downstream *SpdyUpstream::add_pending_downstream(int32_t stream_id,
+                                                 int32_t priority) {
+  auto downstream = make_unique<Downstream>(this, stream_id, priority);
+  auto res = downstream.get();
+
+  downstream_queue_.add_pending(std::move(downstream));
+
+  return res;
+}
+
+void SpdyUpstream::remove_downstream(Downstream *downstream) {
+  if (downstream->accesslog_ready()) {
+    handler_->write_accesslog(downstream);
+  }
+
+  auto next_downstream =
+      downstream_queue_.remove_and_pop_blocked(downstream->get_stream_id());
+
+  if (next_downstream) {
+    initiate_downstream(std::move(next_downstream));
+  }
+}
+
+Downstream *SpdyUpstream::find_downstream(int32_t stream_id) {
+  return downstream_queue_.find(stream_id);
+}
+
+spdylay_session *SpdyUpstream::get_http2_session() { return session_; }
+
+// WARNING: Never call directly or indirectly spdylay_session_send or
+// spdylay_session_recv. These calls may delete downstream.
+int SpdyUpstream::on_downstream_header_complete(Downstream *downstream) {
+  if (downstream->get_non_final_response()) {
+    // SPDY does not support non-final response.  We could send it
+    // with HEADERS and final response in SYN_REPLY, but it is not
+    // official way.
+    downstream->clear_response_headers();
+
+    return 0;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, downstream) << "HTTP response header completed";
+  }
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy &&
+      !get_config()->no_location_rewrite) {
+    downstream->rewrite_location_response_header(
+        downstream->get_request_http2_scheme());
+  }
+  size_t nheader = downstream->get_response_headers().size();
+  // 8 means server, :status, :version and possible via header field.
+  auto nv = make_unique<const char *[]>(
+      nheader * 2 + 8 + get_config()->add_response_headers.size() * 2 + 1);
+
+  size_t hdidx = 0;
+  std::string via_value;
+  std::string status_string =
+      http2::get_status_string(downstream->get_response_http_status());
+  nv[hdidx++] = ":status";
+  nv[hdidx++] = status_string.c_str();
+  nv[hdidx++] = ":version";
+  nv[hdidx++] = "HTTP/1.1";
+  for (auto &hd : downstream->get_response_headers()) {
+    if (hd.name.empty() || hd.name.c_str()[0] == ':') {
+      continue;
+    }
+    switch (hd.token) {
+    case http2::HD_CONNECTION:
+    case http2::HD_KEEP_ALIVE:
+    case http2::HD_PROXY_CONNECTION:
+    case http2::HD_TRANSFER_ENCODING:
+    case http2::HD_VIA:
+    case http2::HD_SERVER:
+      continue;
+    }
+
+    nv[hdidx++] = hd.name.c_str();
+    nv[hdidx++] = hd.value.c_str();
+  }
+
+  if (!get_config()->http2_proxy && !get_config()->client_proxy) {
+    nv[hdidx++] = "server";
+    nv[hdidx++] = get_config()->server_name;
+  } else {
+    auto server = downstream->get_response_header(http2::HD_SERVER);
+    if (server) {
+      nv[hdidx++] = "server";
+      nv[hdidx++] = server->value.c_str();
+    }
+  }
+
+  auto via = downstream->get_response_header(http2::HD_VIA);
+  if (get_config()->no_via) {
+    if (via) {
+      nv[hdidx++] = "via";
+      nv[hdidx++] = via->value.c_str();
+    }
+  } else {
+    if (via) {
+      via_value = via->value;
+      via_value += ", ";
+    }
+    via_value += http::create_via_header_value(
+        downstream->get_response_major(), downstream->get_response_minor());
+    nv[hdidx++] = "via";
+    nv[hdidx++] = via_value.c_str();
+  }
+
+  for (auto &p : get_config()->add_response_headers) {
+    nv[hdidx++] = p.first.c_str();
+    nv[hdidx++] = p.second.c_str();
+  }
+
+  nv[hdidx++] = 0;
+  if (LOG_ENABLED(INFO)) {
+    std::stringstream ss;
+    for (size_t i = 0; nv[i]; i += 2) {
+      ss << TTY_HTTP_HD << nv[i] << TTY_RST << ": " << nv[i + 1] << "\n";
+    }
+    ULOG(INFO, this) << "HTTP response headers. stream_id="
+                     << downstream->get_stream_id() << "\n" << ss.str();
+  }
+  spdylay_data_provider data_prd;
+  data_prd.source.ptr = downstream;
+  data_prd.read_callback = spdy_data_read_callback;
+
+  int rv;
+  rv = spdylay_submit_response(session_, downstream->get_stream_id(), nv.get(),
+                               &data_prd);
+  if (rv != 0) {
+    ULOG(FATAL, this) << "spdylay_submit_response() failed";
+    return -1;
+  }
+
+  return 0;
+}
+
+// WARNING: Never call directly or indirectly spdylay_session_send or
+// spdylay_session_recv. These calls may delete downstream.
+int SpdyUpstream::on_downstream_body(Downstream *downstream,
+                                     const uint8_t *data, size_t len,
+                                     bool flush) {
+  auto body = downstream->get_response_buf();
+  body->append(data, len);
+
+  if (flush) {
+    spdylay_session_resume_data(session_, downstream->get_stream_id());
+
+    downstream->ensure_upstream_wtimer();
+  }
+
+  return 0;
+}
+
+// WARNING: Never call directly or indirectly spdylay_session_send or
+// spdylay_session_recv. These calls may delete downstream.
+int SpdyUpstream::on_downstream_body_complete(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    DLOG(INFO, downstream) << "HTTP response completed";
+  }
+
+  if (!downstream->validate_response_bodylen()) {
+    rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);
+    downstream->set_response_connection_close(true);
+    return 0;
+  }
+
+  spdylay_session_resume_data(session_, downstream->get_stream_id());
+  downstream->ensure_upstream_wtimer();
+
+  return 0;
+}
+
+bool SpdyUpstream::get_flow_control() const { return flow_control_; }
+
+void SpdyUpstream::pause_read(IOCtrlReason reason) {}
+
+int SpdyUpstream::resume_read(IOCtrlReason reason, Downstream *downstream,
+                              size_t consumed) {
+  if (get_flow_control()) {
+    assert(downstream->get_request_datalen() >= consumed);
+
+    if (consume(downstream->get_stream_id(), consumed) != 0) {
+      return -1;
+    }
+
+    downstream->dec_request_datalen(consumed);
+  }
+
+  handler_->signal_write();
+  return 0;
+}
+
+int SpdyUpstream::on_downstream_abort_request(Downstream *downstream,
+                                              unsigned int status_code) {
+  int rv;
+
+  rv = error_reply(downstream, status_code);
+
+  if (rv != 0) {
+    return -1;
+  }
+
+  handler_->signal_write();
+  return 0;
+}
+
+int SpdyUpstream::consume(int32_t stream_id, size_t len) {
+  int rv;
+
+  rv = spdylay_session_consume(session_, stream_id, len);
+
+  if (rv != 0) {
+    ULOG(WARN, this) << "spdylay_session_consume() returned error: "
+                     << spdylay_strerror(rv);
+    return -1;
+  }
+
+  return 0;
+}
+
+int SpdyUpstream::on_timeout(Downstream *downstream) {
+  if (LOG_ENABLED(INFO)) {
+    ULOG(INFO, this) << "Stream timeout stream_id="
+                     << downstream->get_stream_id();
+  }
+
+  rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+
+  return 0;
+}
+
+void SpdyUpstream::on_handler_delete() {
+  for (auto &ent : downstream_queue_.get_active_downstreams()) {
+    if (ent.second->accesslog_ready()) {
+      handler_->write_accesslog(ent.second.get());
+    }
+  }
+}
+
+int SpdyUpstream::on_downstream_reset(bool no_retry) {
+  int rv;
+
+  for (auto &ent : downstream_queue_.get_active_downstreams()) {
+    auto downstream = ent.second.get();
+    if ((downstream->get_request_state() != Downstream::HEADER_COMPLETE &&
+         downstream->get_request_state() != Downstream::MSG_COMPLETE) ||
+        downstream->get_response_state() != Downstream::INITIAL) {
+      rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+      downstream->pop_downstream_connection();
+      continue;
+    }
+
+    downstream->pop_downstream_connection();
+
+    downstream->add_retry();
+
+    if (no_retry || downstream->no_more_retry()) {
+      if (on_downstream_abort_request(downstream, 503) != 0) {
+        return -1;
+      }
+      return 0;
+    }
+
+    // downstream connection is clean; we can retry with new
+    // downstream connection.
+
+    rv = downstream->attach_downstream_connection(
+        handler_->get_downstream_connection());
+    if (rv != 0) {
+      rst_stream(downstream, SPDYLAY_INTERNAL_ERROR);
+      downstream->pop_downstream_connection();
+      continue;
+    }
+  }
+
+  handler_->signal_write();
+
+  return 0;
+}
+
+MemchunkPool *SpdyUpstream::get_mcpool() { return &mcpool_; }
+
+} // namespace shrpx
diff --git a/src/shrpx_spdy_upstream.h b/src/shrpx_spdy_upstream.h
new file mode 100644 (file)
index 0000000..9d83fc6
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_SPDY_UPSTREAM_H
+#define SHRPX_SPDY_UPSTREAM_H
+
+#include "shrpx.h"
+
+#include <memory>
+
+#include <ev.h>
+
+#include <spdylay/spdylay.h>
+
+#include "shrpx_upstream.h"
+#include "shrpx_downstream_queue.h"
+#include "memchunk.h"
+#include "util.h"
+
+namespace shrpx {
+
+class ClientHandler;
+
+class SpdyUpstream : public Upstream {
+public:
+  SpdyUpstream(uint16_t version, ClientHandler *handler);
+  virtual ~SpdyUpstream();
+  virtual int on_read();
+  virtual int on_write();
+  virtual int on_timeout(Downstream *downstream);
+  virtual int on_downstream_abort_request(Downstream *downstream,
+                                          unsigned int status_code);
+  virtual ClientHandler *get_client_handler() const;
+  virtual int downstream_read(DownstreamConnection *dconn);
+  virtual int downstream_write(DownstreamConnection *dconn);
+  virtual int downstream_eof(DownstreamConnection *dconn);
+  virtual int downstream_error(DownstreamConnection *dconn, int events);
+  Downstream *add_pending_downstream(int32_t stream_id, int32_t priority);
+  void remove_downstream(Downstream *downstream);
+  Downstream *find_downstream(int32_t stream_id);
+
+  spdylay_session *get_http2_session();
+
+  int rst_stream(Downstream *downstream, int status_code);
+  int error_reply(Downstream *downstream, unsigned int status_code);
+
+  virtual void pause_read(IOCtrlReason reason);
+  virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+                          size_t consumed);
+
+  virtual int on_downstream_header_complete(Downstream *downstream);
+  virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+                                 size_t len, bool flush);
+  virtual int on_downstream_body_complete(Downstream *downstream);
+
+  virtual void on_handler_delete();
+  virtual int on_downstream_reset(bool no_retry);
+
+  virtual MemchunkPool *get_mcpool();
+
+  bool get_flow_control() const;
+
+  int consume(int32_t stream_id, size_t len);
+
+  void start_downstream(Downstream *downstream);
+  void initiate_downstream(std::unique_ptr<Downstream> downstream);
+
+private:
+  // must be put before downstream_queue_
+  MemchunkPool mcpool_;
+  DownstreamQueue downstream_queue_;
+  ClientHandler *handler_;
+  spdylay_session *session_;
+  int32_t initial_window_size_;
+  bool flow_control_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_SPDY_UPSTREAM_H
diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc
new file mode 100644 (file)
index 0000000..886b63c
--- /dev/null
@@ -0,0 +1,978 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_ssl.h"
+
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <pthread.h>
+
+#include <vector>
+#include <string>
+
+#include <openssl/crypto.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+
+#include <nghttp2/nghttp2.h>
+
+#ifdef HAVE_SPDYLAY
+#include <spdylay/spdylay.h>
+#endif // HAVE_SPDYLAY
+
+#include "shrpx_log.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_config.h"
+#include "shrpx_worker.h"
+#include "shrpx_worker_config.h"
+#include "shrpx_downstream_connection_pool.h"
+#include "util.h"
+#include "ssl.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace ssl {
+
+namespace {
+int next_proto_cb(SSL *s, const unsigned char **data, unsigned int *len,
+                  void *arg) {
+  auto &prefs = get_config()->alpn_prefs;
+  *data = prefs.data();
+  *len = prefs.size();
+  return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+namespace {
+int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
+  if (!preverify_ok) {
+    int err = X509_STORE_CTX_get_error(ctx);
+    int depth = X509_STORE_CTX_get_error_depth(ctx);
+    LOG(ERROR) << "client certificate verify error:num=" << err << ":"
+               << X509_verify_cert_error_string(err) << ":depth=" << depth;
+  }
+  return preverify_ok;
+}
+} // namespace
+
+std::vector<unsigned char> set_alpn_prefs(const std::vector<char *> &protos) {
+  size_t len = 0;
+
+  for (auto proto : protos) {
+    auto n = strlen(proto);
+
+    if (n > 255) {
+      LOG(FATAL) << "Too long ALPN identifier: " << n;
+      DIE();
+    }
+
+    len += 1 + n;
+  }
+
+  if (len > (1 << 16) - 1) {
+    LOG(FATAL) << "Too long ALPN identifier list: " << len;
+    DIE();
+  }
+
+  auto out = std::vector<unsigned char>(len);
+  auto ptr = out.data();
+
+  for (auto proto : protos) {
+    auto proto_len = strlen(proto);
+
+    *ptr++ = proto_len;
+    memcpy(ptr, proto, proto_len);
+    ptr += proto_len;
+  }
+
+  return out;
+}
+
+namespace {
+int ssl_pem_passwd_cb(char *buf, int size, int rwflag, void *user_data) {
+  auto config = static_cast<Config *>(user_data);
+  int len = (int)strlen(config->private_key_passwd.get());
+  if (size < len + 1) {
+    LOG(ERROR) << "ssl_pem_passwd_cb: buf is too small " << size;
+    return 0;
+  }
+  // Copy string including last '\0'.
+  memcpy(buf, config->private_key_passwd.get(), len + 1);
+  return len;
+}
+} // namespace
+
+namespace {
+int servername_callback(SSL *ssl, int *al, void *arg) {
+  auto cert_tree = worker_config->cert_tree;
+  if (cert_tree) {
+    const char *hostname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+    if (hostname) {
+      auto ssl_ctx = cert_tree->lookup(hostname, strlen(hostname));
+      if (ssl_ctx) {
+        SSL_set_SSL_CTX(ssl, ssl_ctx);
+      }
+    }
+  }
+
+  return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+namespace {
+int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
+                  EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) {
+  auto handler = static_cast<ClientHandler *>(SSL_get_app_data(ssl));
+  const auto &ticket_keys = worker_config->ticket_keys;
+
+  if (!ticket_keys) {
+    // No ticket keys available.
+    return -1;
+  }
+
+  auto &keys = ticket_keys->keys;
+  assert(!keys.empty());
+
+  if (enc) {
+    if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) == 0) {
+      if (LOG_ENABLED(INFO)) {
+        CLOG(INFO, handler) << "session ticket key: RAND_bytes failed";
+      }
+      return -1;
+    }
+
+    auto &key = keys[0];
+
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, handler) << "encrypt session ticket key: "
+                          << util::format_hex(key.name, 16);
+    }
+
+    memcpy(key_name, key.name, sizeof(key.name));
+
+    EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, key.aes_key, iv);
+    HMAC_Init_ex(hctx, key.hmac_key, sizeof(key.hmac_key), EVP_sha256(),
+                 nullptr);
+    return 1;
+  }
+
+  size_t i;
+  for (i = 0; i < keys.size(); ++i) {
+    auto &key = keys[0];
+    if (memcmp(key.name, key_name, sizeof(key.name)) == 0) {
+      break;
+    }
+  }
+
+  if (i == keys.size()) {
+    if (LOG_ENABLED(INFO)) {
+      CLOG(INFO, handler) << "session ticket key "
+                          << util::format_hex(key_name, 16) << " not found";
+    }
+    return 0;
+  }
+
+  if (LOG_ENABLED(INFO)) {
+    CLOG(INFO, handler) << "decrypt session ticket key: "
+                        << util::format_hex(key_name, 16);
+  }
+
+  auto &key = keys[i];
+  HMAC_Init_ex(hctx, key.hmac_key, sizeof(key.hmac_key), EVP_sha256(), nullptr);
+  EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, key.aes_key, iv);
+
+  return i == 0 ? 1 : 2;
+}
+} // namespace
+
+namespace {
+void info_callback(const SSL *ssl, int where, int ret) {
+  // To mitigate possible DOS attack using lots of renegotiations, we
+  // disable renegotiation. Since OpenSSL does not provide an easy way
+  // to disable it, we check that renegotiation is started in this
+  // callback.
+  if (where & SSL_CB_HANDSHAKE_START) {
+    auto conn = static_cast<Connection *>(SSL_get_app_data(ssl));
+    if (conn && conn->tls.initial_handshake_done) {
+      // We only set SSL_get_app_data for ClientHandler for now.
+      auto handler = static_cast<ClientHandler *>(conn->data);
+      if (LOG_ENABLED(INFO)) {
+        CLOG(INFO, handler) << "TLS renegotiation started";
+      }
+      handler->start_immediate_shutdown();
+    }
+  }
+}
+} // namespace
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+namespace {
+int alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+                         unsigned char *outlen, const unsigned char *in,
+                         unsigned int inlen, void *arg) {
+  // We assume that get_config()->npn_list contains ALPN protocol
+  // identifier sorted by preference order.  So we just break when we
+  // found the first overlap.
+  for (auto target_proto_id : get_config()->npn_list) {
+    auto target_proto_len =
+        strlen(reinterpret_cast<const char *>(target_proto_id));
+
+    for (auto p = in, end = in + inlen; p < end;) {
+      auto proto_id = p + 1;
+      auto proto_len = *p;
+
+      if (proto_id + proto_len <= end && target_proto_len == proto_len &&
+          memcmp(target_proto_id, proto_id, proto_len) == 0) {
+
+        *out = reinterpret_cast<const unsigned char *>(proto_id);
+        *outlen = proto_len;
+
+        return SSL_TLSEXT_ERR_OK;
+      }
+
+      p += 1 + proto_len;
+    }
+  }
+
+  return SSL_TLSEXT_ERR_NOACK;
+}
+} // namespace
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+
+namespace {
+const char *tls_names[] = {"TLSv1.2", "TLSv1.1", "TLSv1.0"};
+const size_t tls_namelen = array_size(tls_names);
+const long int tls_masks[] = {SSL_OP_NO_TLSv1_2, SSL_OP_NO_TLSv1_1,
+                              SSL_OP_NO_TLSv1};
+} // namespace
+
+long int create_tls_proto_mask(const std::vector<char *> &tls_proto_list) {
+  long int res = 0;
+
+  for (size_t i = 0; i < tls_namelen; ++i) {
+    size_t j;
+    for (j = 0; j < tls_proto_list.size(); ++j) {
+      if (util::strieq(tls_names[i], tls_proto_list[j])) {
+        break;
+      }
+    }
+    if (j == tls_proto_list.size()) {
+      res |= tls_masks[i];
+    }
+  }
+  return res;
+}
+
+SSL_CTX *create_ssl_context(const char *private_key_file,
+                            const char *cert_file) {
+  auto ssl_ctx = SSL_CTX_new(SSLv23_server_method());
+  if (!ssl_ctx) {
+    LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+
+  SSL_CTX_set_options(
+      ssl_ctx,
+      SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION |
+          SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+          SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE |
+          SSL_OP_CIPHER_SERVER_PREFERENCE | get_config()->tls_proto_mask);
+
+  const unsigned char sid_ctx[] = "shrpx";
+  SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
+  SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_SERVER);
+
+  const char *ciphers;
+  if (get_config()->ciphers) {
+    ciphers = get_config()->ciphers.get();
+  } else {
+    ciphers = nghttp2::ssl::DEFAULT_CIPHER_LIST;
+  }
+
+  if (SSL_CTX_set_cipher_list(ssl_ctx, ciphers) == 0) {
+    LOG(FATAL) << "SSL_CTX_set_cipher_list " << ciphers
+               << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+
+#ifndef OPENSSL_NO_EC
+
+  // Disabled SSL_CTX_set_ecdh_auto, because computational cost of
+  // chosen curve is much higher than P-256.
+
+  // #if OPENSSL_VERSION_NUMBER >= 0x10002000L
+  //   SSL_CTX_set_ecdh_auto(ssl_ctx, 1);
+  // #else // OPENSSL_VERSION_NUBMER < 0x10002000L
+  // Use P-256, which is sufficiently secure at the time of this
+  // writing.
+  auto ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+  if (ecdh == nullptr) {
+    LOG(FATAL) << "EC_KEY_new_by_curv_name failed: "
+               << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+  SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh);
+  EC_KEY_free(ecdh);
+// #endif // OPENSSL_VERSION_NUBMER < 0x10002000L
+
+#endif // OPENSSL_NO_EC
+
+  if (get_config()->dh_param_file) {
+    // Read DH parameters from file
+    auto bio = BIO_new_file(get_config()->dh_param_file.get(), "r");
+    if (bio == nullptr) {
+      LOG(FATAL) << "BIO_new_file() failed: "
+                 << ERR_error_string(ERR_get_error(), nullptr);
+      DIE();
+    }
+    auto dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
+    if (dh == nullptr) {
+      LOG(FATAL) << "PEM_read_bio_DHparams() failed: "
+                 << ERR_error_string(ERR_get_error(), nullptr);
+      DIE();
+    }
+    SSL_CTX_set_tmp_dh(ssl_ctx, dh);
+    DH_free(dh);
+    BIO_free(bio);
+  }
+
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+  if (get_config()->private_key_passwd) {
+    SSL_CTX_set_default_passwd_cb(ssl_ctx, ssl_pem_passwd_cb);
+    SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)get_config());
+  }
+  if (SSL_CTX_use_PrivateKey_file(ssl_ctx, private_key_file,
+                                  SSL_FILETYPE_PEM) != 1) {
+    LOG(FATAL) << "SSL_CTX_use_PrivateKey_file failed: "
+               << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+  if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) {
+    LOG(FATAL) << "SSL_CTX_use_certificate_file failed: "
+               << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+  if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
+    LOG(FATAL) << "SSL_CTX_check_private_key failed: "
+               << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+  if (get_config()->verify_client) {
+    if (get_config()->verify_client_cacert) {
+      if (SSL_CTX_load_verify_locations(
+              ssl_ctx, get_config()->verify_client_cacert.get(), nullptr) !=
+          1) {
+
+        LOG(FATAL) << "Could not load trusted ca certificates from "
+                   << get_config()->verify_client_cacert.get() << ": "
+                   << ERR_error_string(ERR_get_error(), nullptr);
+        DIE();
+      }
+      // It is heard that SSL_CTX_load_verify_locations() may leave
+      // error even though it returns success. See
+      // http://forum.nginx.org/read.php?29,242540
+      ERR_clear_error();
+      auto list =
+          SSL_load_client_CA_file(get_config()->verify_client_cacert.get());
+      if (!list) {
+        LOG(FATAL) << "Could not load ca certificates from "
+                   << get_config()->verify_client_cacert.get() << ": "
+                   << ERR_error_string(ERR_get_error(), nullptr);
+        DIE();
+      }
+      SSL_CTX_set_client_CA_list(ssl_ctx, list);
+    }
+    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
+                                    SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                       verify_callback);
+  }
+  SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback);
+  SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
+  SSL_CTX_set_info_callback(ssl_ctx, info_callback);
+
+  // NPN advertisement
+  SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, nullptr);
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+  // ALPN selection callback
+  SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+  return ssl_ctx;
+}
+
+namespace {
+int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
+                         const unsigned char *in, unsigned int inlen,
+                         void *arg) {
+  if (!util::select_h2(const_cast<const unsigned char **>(out), outlen, in,
+                       inlen)) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  return SSL_TLSEXT_ERR_OK;
+}
+} // namespace
+
+SSL_CTX *create_ssl_client_context() {
+  auto ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+  if (!ssl_ctx) {
+    LOG(FATAL) << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+  SSL_CTX_set_options(ssl_ctx,
+                      SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+                          SSL_OP_NO_COMPRESSION |
+                          SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+                          get_config()->tls_proto_mask);
+
+  const char *ciphers;
+  if (get_config()->ciphers) {
+    ciphers = get_config()->ciphers.get();
+  } else {
+    ciphers = "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK";
+  }
+  if (SSL_CTX_set_cipher_list(ssl_ctx, ciphers) == 0) {
+    LOG(FATAL) << "SSL_CTX_set_cipher_list " << ciphers
+               << " failed: " << ERR_error_string(ERR_get_error(), nullptr);
+    DIE();
+  }
+
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
+  SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
+
+  if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
+    LOG(WARN) << "Could not load system trusted ca certificates: "
+              << ERR_error_string(ERR_get_error(), nullptr);
+  }
+
+  if (get_config()->cacert) {
+    if (SSL_CTX_load_verify_locations(ssl_ctx, get_config()->cacert.get(),
+                                      nullptr) != 1) {
+
+      LOG(FATAL) << "Could not load trusted ca certificates from "
+                 << get_config()->cacert.get() << ": "
+                 << ERR_error_string(ERR_get_error(), nullptr);
+      DIE();
+    }
+  }
+
+  if (get_config()->client_private_key_file) {
+    if (SSL_CTX_use_PrivateKey_file(ssl_ctx,
+                                    get_config()->client_private_key_file.get(),
+                                    SSL_FILETYPE_PEM) != 1) {
+      LOG(FATAL) << "Could not load client private key from "
+                 << get_config()->client_private_key_file.get() << ": "
+                 << ERR_error_string(ERR_get_error(), nullptr);
+      DIE();
+    }
+  }
+  if (get_config()->client_cert_file) {
+    if (SSL_CTX_use_certificate_chain_file(
+            ssl_ctx, get_config()->client_cert_file.get()) != 1) {
+
+      LOG(FATAL) << "Could not load client certificate from "
+                 << get_config()->client_cert_file.get() << ": "
+                 << ERR_error_string(ERR_get_error(), nullptr);
+      DIE();
+    }
+  }
+  // NPN selection callback
+  SSL_CTX_set_next_proto_select_cb(ssl_ctx, select_next_proto_cb, nullptr);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+  // ALPN advertisement; We only advertise HTTP/2
+  auto proto_list = util::get_default_alpn();
+
+  SSL_CTX_set_alpn_protos(ssl_ctx, proto_list.data(), proto_list.size());
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+
+  return ssl_ctx;
+}
+
+ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
+                                 sockaddr *addr, int addrlen,
+                                 WorkerStat *worker_stat,
+                                 DownstreamConnectionPool *dconn_pool) {
+  char host[NI_MAXHOST];
+  char service[NI_MAXSERV];
+  int rv;
+  rv = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service),
+                   NI_NUMERICHOST | NI_NUMERICSERV);
+  if (rv != 0) {
+    LOG(ERROR) << "getnameinfo() failed: " << gai_strerror(rv);
+
+    return nullptr;
+  }
+
+  int val = 1;
+  rv = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
+                  sizeof(val));
+  if (rv == -1) {
+    LOG(WARN) << "Setting option TCP_NODELAY failed: errno=" << errno;
+  }
+  SSL *ssl = nullptr;
+  if (ssl_ctx) {
+    ssl = SSL_new(ssl_ctx);
+    if (!ssl) {
+      LOG(ERROR) << "SSL_new() failed: " << ERR_error_string(ERR_get_error(),
+                                                             nullptr);
+      return nullptr;
+    }
+
+    if (SSL_set_fd(ssl, fd) == 0) {
+      LOG(ERROR) << "SSL_set_fd() failed: " << ERR_error_string(ERR_get_error(),
+                                                                nullptr);
+      SSL_free(ssl);
+      return nullptr;
+    }
+
+    SSL_set_accept_state(ssl);
+  }
+
+  return new ClientHandler(loop, fd, ssl, host, service, worker_stat,
+                           dconn_pool);
+}
+
+namespace {
+bool tls_hostname_match(const char *pattern, const char *hostname) {
+  const char *ptWildcard = strchr(pattern, '*');
+  if (ptWildcard == nullptr) {
+    return util::strieq(pattern, hostname);
+  }
+  const char *ptLeftLabelEnd = strchr(pattern, '.');
+  bool wildcardEnabled = true;
+  // Do case-insensitive match. At least 2 dots are required to enable
+  // wildcard match. Also wildcard must be in the left-most label.
+  // Don't attempt to match a presented identifier where the wildcard
+  // character is embedded within an A-label.
+  if (ptLeftLabelEnd == 0 || strchr(ptLeftLabelEnd + 1, '.') == 0 ||
+      ptLeftLabelEnd < ptWildcard || util::istartsWith(pattern, "xn--")) {
+    wildcardEnabled = false;
+  }
+  if (!wildcardEnabled) {
+    return util::strieq(pattern, hostname);
+  }
+  const char *hnLeftLabelEnd = strchr(hostname, '.');
+  if (hnLeftLabelEnd == 0 || !util::strieq(ptLeftLabelEnd, hnLeftLabelEnd)) {
+    return false;
+  }
+  // Perform wildcard match. Here '*' must match at least one
+  // character.
+  if (hnLeftLabelEnd - hostname < ptLeftLabelEnd - pattern) {
+    return false;
+  }
+  return util::istartsWith(hostname, hnLeftLabelEnd, pattern, ptWildcard) &&
+         util::iendsWith(hostname, hnLeftLabelEnd, ptWildcard + 1,
+                         ptLeftLabelEnd);
+}
+} // namespace
+
+namespace {
+int verify_hostname(const char *hostname, const sockaddr_union *su,
+                    size_t salen, const std::vector<std::string> &dns_names,
+                    const std::vector<std::string> &ip_addrs,
+                    const std::string &common_name) {
+  if (util::numeric_host(hostname)) {
+    if (ip_addrs.empty()) {
+      return util::strieq(common_name.c_str(), hostname) ? 0 : -1;
+    }
+    const void *saddr;
+    switch (su->storage.ss_family) {
+    case AF_INET:
+      saddr = &su->in.sin_addr;
+      break;
+    case AF_INET6:
+      saddr = &su->in6.sin6_addr;
+      break;
+    default:
+      return -1;
+    }
+    for (size_t i = 0; i < ip_addrs.size(); ++i) {
+      if (salen == ip_addrs[i].size() &&
+          memcmp(saddr, ip_addrs[i].c_str(), salen) == 0) {
+        return 0;
+      }
+    }
+  } else {
+    if (dns_names.empty()) {
+      return tls_hostname_match(common_name.c_str(), hostname) ? 0 : -1;
+    }
+    for (size_t i = 0; i < dns_names.size(); ++i) {
+      if (tls_hostname_match(dns_names[i].c_str(), hostname)) {
+        return 0;
+      }
+    }
+  }
+  return -1;
+}
+} // namespace
+
+void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
+                  std::vector<std::string> &ip_addrs,
+                  std::string &common_name) {
+  GENERAL_NAMES *altnames = static_cast<GENERAL_NAMES *>(
+      X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
+  if (altnames) {
+    auto altnames_deleter = defer(GENERAL_NAMES_free, altnames);
+    size_t n = sk_GENERAL_NAME_num(altnames);
+    for (size_t i = 0; i < n; ++i) {
+      const GENERAL_NAME *altname = sk_GENERAL_NAME_value(altnames, i);
+      if (altname->type == GEN_DNS) {
+        const char *name;
+        name = reinterpret_cast<char *>(ASN1_STRING_data(altname->d.ia5));
+        if (!name) {
+          continue;
+        }
+        size_t len = ASN1_STRING_length(altname->d.ia5);
+        if (std::find(name, name + len, '\0') != name + len) {
+          // Embedded NULL is not permitted.
+          continue;
+        }
+        dns_names.push_back(std::string(name, len));
+      } else if (altname->type == GEN_IPADD) {
+        const unsigned char *ip_addr = altname->d.iPAddress->data;
+        if (!ip_addr) {
+          continue;
+        }
+        size_t len = altname->d.iPAddress->length;
+        ip_addrs.push_back(
+            std::string(reinterpret_cast<const char *>(ip_addr), len));
+      }
+    }
+  }
+  X509_NAME *subjectname = X509_get_subject_name(cert);
+  if (!subjectname) {
+    LOG(WARN) << "Could not get X509 name object from the certificate.";
+    return;
+  }
+  int lastpos = -1;
+  while (1) {
+    lastpos = X509_NAME_get_index_by_NID(subjectname, NID_commonName, lastpos);
+    if (lastpos == -1) {
+      break;
+    }
+    X509_NAME_ENTRY *entry = X509_NAME_get_entry(subjectname, lastpos);
+    unsigned char *out;
+    int outlen = ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(entry));
+    if (outlen < 0) {
+      continue;
+    }
+    if (std::find(out, out + outlen, '\0') != out + outlen) {
+      // Embedded NULL is not permitted.
+      continue;
+    }
+    common_name.assign(&out[0], &out[outlen]);
+    OPENSSL_free(out);
+    break;
+  }
+}
+
+int check_cert(SSL *ssl) {
+  auto cert = SSL_get_peer_certificate(ssl);
+  if (!cert) {
+    LOG(ERROR) << "No certificate found";
+    return -1;
+  }
+  auto cert_deleter = defer(X509_free, cert);
+  long verify_res = SSL_get_verify_result(ssl);
+  if (verify_res != X509_V_OK) {
+    LOG(ERROR) << "Certificate verification failed: "
+               << X509_verify_cert_error_string(verify_res);
+    return -1;
+  }
+  std::string common_name;
+  std::vector<std::string> dns_names;
+  std::vector<std::string> ip_addrs;
+  get_altnames(cert, dns_names, ip_addrs, common_name);
+  if (verify_hostname(get_config()->downstream_addrs[0].host.get(),
+                      &get_config()->downstream_addrs[0].addr,
+                      get_config()->downstream_addrs[0].addrlen, dns_names,
+                      ip_addrs, common_name) != 0) {
+    LOG(ERROR) << "Certificate verification failed: hostname does not match";
+    return -1;
+  }
+  return 0;
+}
+
+CertLookupTree::CertLookupTree() {
+  root_.ssl_ctx = nullptr;
+  root_.str = nullptr;
+  root_.first = root_.last = 0;
+}
+
+namespace {
+// The |offset| is the index in the hostname we are examining.  We are
+// going to scan from |offset| in backwards.
+void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx, char *hostname,
+                               size_t len, int offset) {
+  int i, next_len = node->next.size();
+  char c = hostname[offset];
+  CertNode *cn = nullptr;
+  for (i = 0; i < next_len; ++i) {
+    cn = node->next[i].get();
+    if (cn->str[cn->first] == c) {
+      break;
+    }
+  }
+  if (i == next_len) {
+    if (c == '*') {
+      // We assume hostname as wildcard hostname when first '*' is
+      // encountered. Note that as per RFC 6125 (6.4.3), there are
+      // some restrictions for wildcard hostname. We just ignore
+      // these rules here but do the proper check when we do the
+      // match.
+      node->wildcard_certs.emplace_back(hostname, ssl_ctx);
+      return;
+    }
+
+    int j;
+    auto new_node = make_unique<CertNode>();
+    new_node->str = hostname;
+    new_node->first = offset;
+    // If wildcard is found, set the region before it because we
+    // don't include it in [first, last).
+    for (j = offset; j >= 0 && hostname[j] != '*'; --j)
+      ;
+    new_node->last = j;
+    if (j == -1) {
+      new_node->ssl_ctx = ssl_ctx;
+    } else {
+      new_node->ssl_ctx = nullptr;
+      new_node->wildcard_certs.emplace_back(hostname, ssl_ctx);
+    }
+    node->next.push_back(std::move(new_node));
+    return;
+  }
+
+  int j;
+  for (i = cn->first, j = offset;
+       i > cn->last && j >= 0 && cn->str[i] == hostname[j]; --i, --j)
+    ;
+  if (i == cn->last) {
+    if (j == -1) {
+      // If the same hostname already exists, we don't overwrite
+      // exiting ssl_ctx
+      if (!cn->ssl_ctx) {
+        cn->ssl_ctx = ssl_ctx;
+      }
+      return;
+    }
+
+    // The existing hostname is a suffix of this hostname.  Continue
+    // matching at potion j.
+    cert_lookup_tree_add_cert(cn, ssl_ctx, hostname, len, j);
+    return;
+  }
+
+  {
+    auto new_node = make_unique<CertNode>();
+    new_node->ssl_ctx = cn->ssl_ctx;
+    new_node->str = cn->str;
+    new_node->first = i;
+    new_node->last = cn->last;
+    new_node->wildcard_certs.swap(cn->wildcard_certs);
+    new_node->next.swap(cn->next);
+
+    cn->next.push_back(std::move(new_node));
+  }
+
+  cn->last = i;
+  if (j == -1) {
+    // This hostname is a suffix of the existing hostname.
+    cn->ssl_ctx = ssl_ctx;
+    return;
+  }
+
+  // This hostname and existing one share suffix.
+  cn->ssl_ctx = nullptr;
+  cert_lookup_tree_add_cert(cn, ssl_ctx, hostname, len, j);
+}
+} // namespace
+
+void CertLookupTree::add_cert(SSL_CTX *ssl_ctx, const char *hostname,
+                              size_t len) {
+  if (len == 0) {
+    return;
+  }
+  // Copy hostname including terminal NULL
+  hosts_.push_back(make_unique<char[]>(len + 1));
+  const auto &host_copy = hosts_.back();
+  for (size_t i = 0; i < len; ++i) {
+    host_copy[i] = util::lowcase(hostname[i]);
+  }
+  host_copy[len] = '\0';
+  cert_lookup_tree_add_cert(&root_, ssl_ctx, host_copy.get(), len, len - 1);
+}
+
+namespace {
+SSL_CTX *cert_lookup_tree_lookup(CertNode *node, const char *hostname,
+                                 size_t len, int offset) {
+  int i, j;
+  for (i = node->first, j = offset;
+       i > node->last && j >= 0 && node->str[i] == util::lowcase(hostname[j]);
+       --i, --j)
+    ;
+  if (i != node->last) {
+    return nullptr;
+  }
+  if (j == -1) {
+    if (node->ssl_ctx) {
+      // exact match
+      return node->ssl_ctx;
+    }
+
+    // Do not perform wildcard-match because '*' must match at least
+    // one character.
+    return nullptr;
+  }
+  for (const auto &wildcert : node->wildcard_certs) {
+    if (tls_hostname_match(wildcert.first, hostname)) {
+      return wildcert.second;
+    }
+  }
+  auto c = util::lowcase(hostname[j]);
+  for (const auto &next_node : node->next) {
+    if (next_node->str[next_node->first] == c) {
+      return cert_lookup_tree_lookup(next_node.get(), hostname, len, j);
+    }
+  }
+  return nullptr;
+}
+} // namespace
+
+SSL_CTX *CertLookupTree::lookup(const char *hostname, size_t len) {
+  return cert_lookup_tree_lookup(&root_, hostname, len, len - 1);
+}
+
+int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
+                                        const char *certfile) {
+  auto bio = BIO_new(BIO_s_file());
+  if (!bio) {
+    LOG(ERROR) << "BIO_new failed";
+    return -1;
+  }
+  auto bio_deleter = defer(BIO_vfree, bio);
+  if (!BIO_read_filename(bio, certfile)) {
+    LOG(ERROR) << "Could not read certificate file '" << certfile << "'";
+    return -1;
+  }
+  auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
+  if (!cert) {
+    LOG(ERROR) << "Could not read X509 structure from file '" << certfile
+               << "'";
+    return -1;
+  }
+  auto cert_deleter = defer(X509_free, cert);
+  std::string common_name;
+  std::vector<std::string> dns_names;
+  std::vector<std::string> ip_addrs;
+  get_altnames(cert, dns_names, ip_addrs, common_name);
+  for (auto &dns_name : dns_names) {
+    lt->add_cert(ssl_ctx, dns_name.c_str(), dns_name.size());
+  }
+  lt->add_cert(ssl_ctx, common_name.c_str(), common_name.size());
+  return 0;
+}
+
+bool in_proto_list(const std::vector<char *> &protos,
+                   const unsigned char *needle, size_t len) {
+  for (auto proto : protos) {
+    if (strlen(proto) == len && memcmp(proto, needle, len) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool check_http2_requirement(SSL *ssl) {
+  auto tls_ver = SSL_version(ssl);
+
+  switch (tls_ver) {
+  case TLS1_2_VERSION:
+    break;
+  default:
+    if (LOG_ENABLED(INFO)) {
+      LOG(INFO) << "TLSv1.2 was not negotiated. "
+                << "HTTP/2 must not be negotiated.";
+    }
+    return false;
+  }
+
+  return true;
+}
+
+SSL_CTX *setup_server_ssl_context() {
+  if (get_config()->upstream_no_tls) {
+    return nullptr;
+  }
+
+  auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(),
+                                         get_config()->cert_file.get());
+
+  if (get_config()->subcerts.empty()) {
+    return ssl_ctx;
+  }
+
+  auto cert_tree = new CertLookupTree();
+
+  worker_config->cert_tree = cert_tree;
+
+  for (auto &keycert : get_config()->subcerts) {
+    auto ssl_ctx =
+        ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str());
+    if (ssl::cert_lookup_tree_add_cert_from_file(
+            cert_tree, ssl_ctx, keycert.second.c_str()) == -1) {
+      LOG(FATAL) << "Failed to add sub certificate.";
+      DIE();
+    }
+  }
+
+  if (ssl::cert_lookup_tree_add_cert_from_file(
+          cert_tree, ssl_ctx, get_config()->cert_file.get()) == -1) {
+    LOG(FATAL) << "Failed to add default certificate.";
+    DIE();
+  }
+
+  return ssl_ctx;
+}
+
+SSL_CTX *setup_client_ssl_context() {
+  if (get_config()->client_mode) {
+    return get_config()->downstream_no_tls ? nullptr
+                                           : ssl::create_ssl_client_context();
+  }
+
+  return get_config()->http2_bridge && !get_config()->downstream_no_tls
+             ? ssl::create_ssl_client_context()
+             : nullptr;
+}
+
+} // namespace ssl
+
+} // namespace shrpx
diff --git a/src/shrpx_ssl.h b/src/shrpx_ssl.h
new file mode 100644 (file)
index 0000000..68d3182
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_SSL_H
+#define SHRPX_SSL_H
+
+#include "shrpx.h"
+
+#include <vector>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <ev.h>
+
+namespace shrpx {
+
+class ClientHandler;
+struct WorkerStat;
+class DownstreamConnectionPool;
+
+namespace ssl {
+
+// Create server side SSL_CTX
+SSL_CTX *create_ssl_context(const char *private_key_file,
+                            const char *cert_file);
+
+// Create client side SSL_CTX
+SSL_CTX *create_ssl_client_context();
+
+ClientHandler *accept_connection(struct ev_loop *loop, SSL_CTX *ssl_ctx, int fd,
+                                 sockaddr *addr, int addrlen,
+                                 WorkerStat *worker_stat,
+                                 DownstreamConnectionPool *dconn_pool);
+
+// Check peer's certificate against first downstream address in
+// Config::downstream_addrs.  We only consider first downstream since
+// we use this function for HTTP/2 downstream link only.
+int check_cert(SSL *ssl);
+
+// Retrieves DNS and IP address in subjectAltNames and commonName from
+// the |cert|.
+void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
+                  std::vector<std::string> &ip_addrs, std::string &common_name);
+
+// CertLookupTree forms lookup tree to get SSL_CTX whose DNS or
+// commonName matches hostname in query. The tree is patricia trie
+// data structure formed from the tail of the hostname pattern. Each
+// CertNode contains part of hostname str member in range [first,
+// last) member and the next member contains the following CertNode
+// pointers ('following' means character before the current one). The
+// CertNode where a hostname pattern ends contains its SSL_CTX pointer
+// in the ssl_ctx member.  For wildcard hostname pattern, we store the
+// its pattern and SSL_CTX in CertNode one before first "*" found from
+// the tail.
+//
+// When querying SSL_CTX with particular hostname, we match from its
+// tail in our lookup tree. If the query goes to the first character
+// of the hostname and current CertNode has non-NULL ssl_ctx member,
+// then it is the exact match. The ssl_ctx member is returned.  Along
+// the way, if CertNode which contains non-empty wildcard_certs member
+// is encountered, wildcard hostname matching is performed against
+// them. If there is a match, its SSL_CTX is returned. If none
+// matches, query is continued to the next character.
+
+struct CertNode {
+  // list of wildcard domain name and its SSL_CTX pair, the wildcard
+  // '*' appears in this position.
+  std::vector<std::pair<char *, SSL_CTX *>> wildcard_certs;
+  // Next CertNode index of CertLookupTree::nodes
+  std::vector<std::unique_ptr<CertNode>> next;
+  // SSL_CTX for exact match
+  SSL_CTX *ssl_ctx;
+  char *str;
+  // [first, last) in the reverse direction in str, first >=
+  // last. This indices only work for str member.
+  int first, last;
+};
+
+class CertLookupTree {
+public:
+  CertLookupTree();
+
+  // Adds |ssl_ctx| with hostname pattern |hostname| with length |len|
+  // to the lookup tree.  The |hostname| must be NULL-terminated.
+  void add_cert(SSL_CTX *ssl_ctx, const char *hostname, size_t len);
+
+  // Looks up SSL_CTX using the given |hostname| with length |len|.
+  // If more than one SSL_CTX which matches the query, it is undefined
+  // which one is returned.  The |hostname| must be NULL-terminated.
+  // If no matching SSL_CTX found, returns NULL.
+  SSL_CTX *lookup(const char *hostname, size_t len);
+
+private:
+  CertNode root_;
+  // Stores pointers to copied hostname when adding hostname and
+  // ssl_ctx pair.
+  std::vector<std::unique_ptr<char[]>> hosts_;
+};
+
+// Adds |ssl_ctx| to lookup tree |lt| using hostnames read from
+// |certfile|. The subjectAltNames and commonName are considered as
+// eligible hostname. This function returns 0 if it succeeds, or -1.
+// Even if no ssl_ctx is added to tree, this function returns 0.
+int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
+                                        const char *certfile);
+
+// Returns true if |needle| which has |len| bytes is included in the
+// protocol list |protos|.
+bool in_proto_list(const std::vector<char *> &protos,
+                   const unsigned char *needle, size_t len);
+
+// Returns true if security requirement for HTTP/2 is fulfilled.
+bool check_http2_requirement(SSL *ssl);
+
+// Returns SSL/TLS option mask to disable SSL/TLS protocol version not
+// included in |tls_proto_list|.  The returned mask can be directly
+// passed to SSL_CTX_set_options().
+long int create_tls_proto_mask(const std::vector<char *> &tls_proto_list);
+
+std::vector<unsigned char> set_alpn_prefs(const std::vector<char *> &protos);
+
+// Setups server side SSL_CTX.  This function inspects get_config()
+// and if upstream_no_tls is true, returns nullptr.  Otherwise
+// construct default SSL_CTX.  If subcerts are not empty, create
+// SSL_CTX for them.  All created SSL_CTX are added to CertLookupTree.
+SSL_CTX *setup_server_ssl_context();
+
+// Setups client side SSL_CTX.  This function inspects get_config()
+// and if downstream_no_tls is true, returns nullptr.  Otherwise, only
+// construct SSL_CTX if either client_mode or http2_bridge is true.
+SSL_CTX *setup_client_ssl_context();
+
+} // namespace ssl
+
+} // namespace shrpx
+
+#endif // SHRPX_SSL_H
diff --git a/src/shrpx_ssl_test.cc b/src/shrpx_ssl_test.cc
new file mode 100644 (file)
index 0000000..22bac63
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_ssl_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "shrpx_ssl.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+void test_shrpx_ssl_create_lookup_tree(void) {
+  auto tree = make_unique<ssl::CertLookupTree>();
+  SSL_CTX *ctxs[] = {
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())};
+
+  const char *hostnames[] = {"example.com",      "www.example.org",
+                             "*www.example.org", "x*.host.domain",
+                             "*yy.host.domain",  "nghttp2.sourceforge.net",
+                             "sourceforge.net",
+                             "sourceforge.net", // duplicate
+                             "*.foo.bar",       // oo.bar is suffix of *.foo.bar
+                             "oo.bar"};
+  int num = array_size(ctxs);
+  for (int i = 0; i < num; ++i) {
+    tree->add_cert(ctxs[i], hostnames[i], strlen(hostnames[i]));
+  }
+
+  CU_ASSERT(ctxs[0] == tree->lookup(hostnames[0], strlen(hostnames[0])));
+  CU_ASSERT(ctxs[1] == tree->lookup(hostnames[1], strlen(hostnames[1])));
+  const char h1[] = "2www.example.org";
+  CU_ASSERT(ctxs[2] == tree->lookup(h1, strlen(h1)));
+  const char h2[] = "www2.example.org";
+  CU_ASSERT(0 == tree->lookup(h2, strlen(h2)));
+  const char h3[] = "x1.host.domain";
+  CU_ASSERT(ctxs[3] == tree->lookup(h3, strlen(h3)));
+  // Does not match *yy.host.domain, because * must match at least 1
+  // character.
+  const char h4[] = "yy.Host.domain";
+  CU_ASSERT(0 == tree->lookup(h4, strlen(h4)));
+  const char h5[] = "zyy.host.domain";
+  CU_ASSERT(ctxs[4] == tree->lookup(h5, strlen(h5)));
+  CU_ASSERT(0 == tree->lookup("", 0));
+  CU_ASSERT(ctxs[5] == tree->lookup(hostnames[5], strlen(hostnames[5])));
+  CU_ASSERT(ctxs[6] == tree->lookup(hostnames[6], strlen(hostnames[6])));
+  const char h6[] = "pdylay.sourceforge.net";
+  for (int i = 0; i < 7; ++i) {
+    CU_ASSERT(0 == tree->lookup(h6 + i, strlen(h6) - i));
+  }
+  const char h7[] = "x.foo.bar";
+  CU_ASSERT(ctxs[8] == tree->lookup(h7, strlen(h7)));
+  CU_ASSERT(ctxs[9] == tree->lookup(hostnames[9], strlen(hostnames[9])));
+
+  for (int i = 0; i < num; ++i) {
+    SSL_CTX_free(ctxs[i]);
+  }
+
+  SSL_CTX *ctxs2[] = {
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
+      SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())};
+  const char *names[] = {"rab", "zab", "zzub", "ab"};
+  num = array_size(ctxs2);
+
+  tree = make_unique<ssl::CertLookupTree>();
+  for (int i = 0; i < num; ++i) {
+    tree->add_cert(ctxs2[i], names[i], strlen(names[i]));
+  }
+  for (int i = 0; i < num; ++i) {
+    CU_ASSERT(ctxs2[i] == tree->lookup(names[i], strlen(names[i])));
+  }
+
+  for (int i = 0; i < num; ++i) {
+    SSL_CTX_free(ctxs2[i]);
+  }
+}
+
+void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void) {
+  int rv;
+  ssl::CertLookupTree tree;
+  auto ssl_ctx = SSL_CTX_new(SSLv23_method());
+  const char certfile[] = NGHTTP2_TESTS_DIR "/testdata/cacert.pem";
+  rv = ssl::cert_lookup_tree_add_cert_from_file(&tree, ssl_ctx, certfile);
+  CU_ASSERT(0 == rv);
+  const char localhost[] = "localhost";
+  CU_ASSERT(ssl_ctx == tree.lookup(localhost, sizeof(localhost) - 1));
+
+  SSL_CTX_free(ssl_ctx);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_ssl_test.h b/src/shrpx_ssl_test.h
new file mode 100644 (file)
index 0000000..fbb1fa6
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_SSL_TEST_H
+#define SHRPX_SSL_TEST_H
+
+namespace shrpx {
+
+void test_shrpx_ssl_create_lookup_tree(void);
+void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void);
+
+} // namespace shrpx
+
+#endif // SHRPX_SSL_TEST_H
diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h
new file mode 100644 (file)
index 0000000..ad8e742
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_UPSTREAM_H
+#define SHRPX_UPSTREAM_H
+
+#include "shrpx.h"
+#include "shrpx_io_control.h"
+#include "memchunk.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+class ClientHandler;
+class Downstream;
+class DownstreamConnection;
+
+class Upstream {
+public:
+  virtual ~Upstream() {}
+  virtual int on_read() = 0;
+  virtual int on_write() = 0;
+  virtual int on_timeout(Downstream *downstream) { return 0; };
+  virtual int on_downstream_abort_request(Downstream *downstream,
+                                          unsigned int status_code) = 0;
+  virtual int downstream_read(DownstreamConnection *dconn) = 0;
+  virtual int downstream_write(DownstreamConnection *dconn) = 0;
+  virtual int downstream_eof(DownstreamConnection *dconn) = 0;
+  virtual int downstream_error(DownstreamConnection *dconn, int events) = 0;
+  virtual ClientHandler *get_client_handler() const = 0;
+
+  virtual int on_downstream_header_complete(Downstream *downstream) = 0;
+  virtual int on_downstream_body(Downstream *downstream, const uint8_t *data,
+                                 size_t len, bool flush) = 0;
+  virtual int on_downstream_body_complete(Downstream *downstream) = 0;
+
+  virtual void on_handler_delete() = 0;
+  // Called when downstream connection is reset.  Currently this is
+  // only used by Http2Session.  If |no_retry| is true, another
+  // connection attempt using new DownstreamConnection is not allowed.
+  virtual int on_downstream_reset(bool no_retry) = 0;
+
+  virtual void pause_read(IOCtrlReason reason) = 0;
+  virtual int resume_read(IOCtrlReason reason, Downstream *downstream,
+                          size_t consumed) = 0;
+
+  virtual MemchunkPool *get_mcpool() = 0;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_UPSTREAM_H
diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc
new file mode 100644 (file)
index 0000000..9f5682c
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_worker.h"
+
+#include <unistd.h>
+
+#include <memory>
+
+#include "shrpx_ssl.h"
+#include "shrpx_log.h"
+#include "shrpx_client_handler.h"
+#include "shrpx_http2_session.h"
+#include "shrpx_worker_config.h"
+#include "shrpx_connect_blocker.h"
+#include "util.h"
+#include "template.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+namespace {
+void eventcb(struct ev_loop *loop, ev_async *w, int revents) {
+  auto worker = static_cast<Worker *>(w->data);
+  worker->process_events();
+}
+} // namespace
+
+Worker::Worker(SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
+               ssl::CertLookupTree *cert_tree,
+               const std::shared_ptr<TicketKeys> &ticket_keys)
+    : loop_(ev_loop_new(0)), sv_ssl_ctx_(sv_ssl_ctx), cl_ssl_ctx_(cl_ssl_ctx),
+      worker_stat_(make_unique<WorkerStat>()) {
+  ev_async_init(&w_, eventcb);
+  w_.data = this;
+  ev_async_start(loop_, &w_);
+
+#ifndef NOTHREADS
+  fut_ = std::async(std::launch::async, [this, cert_tree, &ticket_keys] {
+    if (get_config()->tls_ctx_per_worker) {
+      sv_ssl_ctx_ = ssl::setup_server_ssl_context();
+      cl_ssl_ctx_ = ssl::setup_client_ssl_context();
+    } else {
+      worker_config->cert_tree = cert_tree;
+    }
+
+    if (get_config()->downstream_proto == PROTO_HTTP2) {
+      http2session_ = make_unique<Http2Session>(loop_, cl_ssl_ctx_);
+    } else {
+      http1_connect_blocker_ = make_unique<ConnectBlocker>(loop_);
+    }
+
+    worker_config->ticket_keys = ticket_keys;
+    (void)reopen_log_files();
+    ev_run(loop_);
+  });
+#endif // !NOTHREADS
+}
+
+Worker::~Worker() { ev_async_stop(loop_, &w_); }
+
+void Worker::wait() {
+#ifndef NOTHREADS
+  fut_.get();
+#endif // !NOTHREADS
+}
+
+void Worker::send(const WorkerEvent &event) {
+  {
+    std::lock_guard<std::mutex> g(m_);
+
+    q_.push_back(event);
+  }
+
+  ev_async_send(loop_, &w_);
+}
+
+void Worker::process_events() {
+  std::deque<WorkerEvent> q;
+  {
+    std::lock_guard<std::mutex> g(m_);
+    q.swap(q_);
+  }
+  for (auto &wev : q) {
+    switch (wev.type) {
+    case NEW_CONNECTION: {
+      if (LOG_ENABLED(INFO)) {
+        WLOG(INFO, this) << "WorkerEvent: client_fd=" << wev.client_fd
+                         << ", addrlen=" << wev.client_addrlen;
+      }
+
+      if (worker_stat_->num_connections >=
+          get_config()->worker_frontend_connections) {
+
+        if (LOG_ENABLED(INFO)) {
+          WLOG(INFO, this) << "Too many connections >= "
+                           << get_config()->worker_frontend_connections;
+        }
+
+        close(wev.client_fd);
+
+        break;
+      }
+
+      auto client_handler = ssl::accept_connection(
+          loop_, sv_ssl_ctx_, wev.client_fd, &wev.client_addr.sa,
+          wev.client_addrlen, worker_stat_.get(), &dconn_pool_);
+      if (!client_handler) {
+        if (LOG_ENABLED(INFO)) {
+          WLOG(ERROR, this) << "ClientHandler creation failed";
+        }
+        close(wev.client_fd);
+        break;
+      }
+
+      client_handler->set_http2_session(http2session_.get());
+      client_handler->set_http1_connect_blocker(http1_connect_blocker_.get());
+
+      if (LOG_ENABLED(INFO)) {
+        WLOG(INFO, this) << "CLIENT_HANDLER:" << client_handler << " created ";
+      }
+
+      break;
+    }
+    case RENEW_TICKET_KEYS:
+      if (LOG_ENABLED(INFO)) {
+        WLOG(INFO, this) << "Renew ticket keys: worker_info(" << worker_config
+                         << ")";
+      }
+
+      worker_config->ticket_keys = wev.ticket_keys;
+
+      break;
+    case REOPEN_LOG:
+      if (LOG_ENABLED(INFO)) {
+        WLOG(INFO, this) << "Reopening log files: worker_info(" << worker_config
+                         << ")";
+      }
+
+      reopen_log_files();
+
+      break;
+    case GRACEFUL_SHUTDOWN:
+      WLOG(NOTICE, this) << "Graceful shutdown commencing";
+
+      worker_config->graceful_shutdown = true;
+
+      if (worker_stat_->num_connections == 0) {
+        ev_break(loop_);
+
+        return;
+      }
+
+      break;
+    default:
+      if (LOG_ENABLED(INFO)) {
+        WLOG(INFO, this) << "unknown event type " << wev.type;
+      }
+    }
+  }
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h
new file mode 100644 (file)
index 0000000..cb527ed
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_WORKER_H
+#define SHRPX_WORKER_H
+
+#include "shrpx.h"
+
+#include <mutex>
+#include <deque>
+#include <thread>
+#ifndef NOTHREADS
+#include <future>
+#endif // NOTHREADS
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include <ev.h>
+
+#include "shrpx_config.h"
+#include "shrpx_downstream_connection_pool.h"
+
+namespace shrpx {
+
+class Http2Session;
+class ConnectBlocker;
+
+namespace ssl {
+class CertLookupTree;
+} // namespace ssl
+
+struct WorkerStat {
+  WorkerStat() : num_connections(0), next_downstream(0) {}
+
+  size_t num_connections;
+  // Next downstream index in Config::downstream_addrs.  For HTTP/2
+  // downstream connections, this is always 0.  For HTTP/1, this is
+  // used as load balancing.
+  size_t next_downstream;
+};
+
+enum WorkerEventType {
+  NEW_CONNECTION = 0x01,
+  REOPEN_LOG = 0x02,
+  GRACEFUL_SHUTDOWN = 0x03,
+  RENEW_TICKET_KEYS = 0x04,
+};
+
+struct WorkerEvent {
+  WorkerEventType type;
+  struct {
+    sockaddr_union client_addr;
+    size_t client_addrlen;
+    int client_fd;
+  };
+  std::shared_ptr<TicketKeys> ticket_keys;
+};
+
+class Worker {
+public:
+  Worker(SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx,
+         ssl::CertLookupTree *cert_tree,
+         const std::shared_ptr<TicketKeys> &ticket_keys);
+  ~Worker();
+  void wait();
+  void process_events();
+  void send(const WorkerEvent &event);
+
+private:
+#ifndef NOTHREADS
+  std::future<void> fut_;
+#endif // NOTHREADS
+  std::mutex m_;
+  std::deque<WorkerEvent> q_;
+  ev_async w_;
+  DownstreamConnectionPool dconn_pool_;
+  struct ev_loop *loop_;
+  SSL_CTX *sv_ssl_ctx_;
+  SSL_CTX *cl_ssl_ctx_;
+  std::unique_ptr<Http2Session> http2session_;
+  std::unique_ptr<ConnectBlocker> http1_connect_blocker_;
+  std::unique_ptr<WorkerStat> worker_stat_;
+};
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_H
diff --git a/src/shrpx_worker_config.cc b/src/shrpx_worker_config.cc
new file mode 100644 (file)
index 0000000..4e95b23
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "shrpx_worker_config.h"
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+WorkerConfig::WorkerConfig()
+    : cert_tree(nullptr), accesslog_fd(-1), errorlog_fd(-1),
+      errorlog_tty(false), graceful_shutdown(false) {}
+
+#ifndef NOTHREADS
+thread_local
+#endif // NOTHREADS
+    WorkerConfig *worker_config = new WorkerConfig();
+
+void
+WorkerConfig::update_tstamp(const std::chrono::system_clock::time_point &now) {
+  auto t0 = std::chrono::system_clock::to_time_t(time_str_updated_);
+  auto t1 = std::chrono::system_clock::to_time_t(now);
+  if (t0 == t1) {
+    return;
+  }
+
+  time_str_updated_ = now;
+
+  time_local_str = util::format_common_log(now);
+  time_iso8601_str = util::format_iso8601(now);
+}
+
+} // namespace shrpx
diff --git a/src/shrpx_worker_config.h b/src/shrpx_worker_config.h
new file mode 100644 (file)
index 0000000..b281c2f
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef SHRPX_WORKER_CONFIG_H
+#define SHRPX_WORKER_CONFIG_H
+
+#include "shrpx.h"
+
+#include <chrono>
+
+namespace shrpx {
+
+namespace ssl {
+class CertLookupTree;
+} // namespace ssl
+
+struct TicketKeys;
+
+struct WorkerConfig {
+  std::shared_ptr<TicketKeys> ticket_keys;
+  std::chrono::system_clock::time_point time_str_updated_;
+  std::string time_local_str;
+  std::string time_iso8601_str;
+  ssl::CertLookupTree *cert_tree;
+  int accesslog_fd;
+  int errorlog_fd;
+  // true if errorlog_fd is referring to a terminal.
+  bool errorlog_tty;
+  bool graceful_shutdown;
+
+  WorkerConfig();
+  void update_tstamp(const std::chrono::system_clock::time_point &now);
+};
+
+// We need WorkerConfig per thread
+extern
+#ifndef NOTHREADS
+    thread_local
+#endif // NOTHREADS
+    WorkerConfig *worker_config;
+
+} // namespace shrpx
+
+#endif // SHRPX_WORKER_CONFIG_H
diff --git a/src/ssl.cc b/src/ssl.cc
new file mode 100644 (file)
index 0000000..8696801
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "ssl.h"
+
+#include <cassert>
+#include <vector>
+#include <mutex>
+#include <iostream>
+
+#include <openssl/crypto.h>
+
+namespace nghttp2 {
+
+namespace ssl {
+
+// Recommended general purpose "Non-Backward Compatible" cipher by
+// mozilla.
+//
+// https://wiki.mozilla.org/Security/Server_Side_TLS
+const char *const DEFAULT_CIPHER_LIST =
+    "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-"
+    "AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:"
+    "DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-"
+    "AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-"
+    "AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-"
+    "AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:"
+    "DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:"
+    "!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK";
+
+namespace {
+std::vector<std::mutex> ssl_global_locks;
+} // namespace
+
+namespace {
+void ssl_locking_cb(int mode, int type, const char *file, int line) {
+  if (mode & CRYPTO_LOCK) {
+    ssl_global_locks[type].lock();
+  } else {
+    ssl_global_locks[type].unlock();
+  }
+}
+} // namespace
+
+LibsslGlobalLock::LibsslGlobalLock() {
+  if (!ssl_global_locks.empty()) {
+    std::cerr << "OpenSSL global lock has been already set" << std::endl;
+    assert(0);
+  }
+  ssl_global_locks = std::vector<std::mutex>(CRYPTO_num_locks());
+  // CRYPTO_set_id_callback(ssl_thread_id); OpenSSL manual says that
+  // if threadid_func is not specified using
+  // CRYPTO_THREADID_set_callback(), then default implementation is
+  // used. We use this default one.
+  CRYPTO_set_locking_callback(ssl_locking_cb);
+}
+
+LibsslGlobalLock::~LibsslGlobalLock() { ssl_global_locks.clear(); }
+
+} // namespace ssl
+
+} // namespace nghttp2
diff --git a/src/ssl.h b/src/ssl.h
new file mode 100644 (file)
index 0000000..97f3933
--- /dev/null
+++ b/src/ssl.h
@@ -0,0 +1,45 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_config.h"
+
+namespace nghttp2 {
+
+namespace ssl {
+
+// Acquire OpenSSL global lock to share SSL_CTX across multiple
+// threads. The constructor acquires lock and destructor unlocks.
+class LibsslGlobalLock {
+public:
+  LibsslGlobalLock();
+  ~LibsslGlobalLock();
+  LibsslGlobalLock(const LibsslGlobalLock &) = delete;
+  LibsslGlobalLock &operator=(const LibsslGlobalLock &) = delete;
+};
+
+extern const char *const DEFAULT_CIPHER_LIST;
+
+} // namespace ssl
+
+} // namespace nghttp2
diff --git a/src/template.h b/src/template.h
new file mode 100644 (file)
index 0000000..c1fea1d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2015 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef TEMPLATE_H
+#define TEMPLATE_H
+
+#include "nghttp2_config.h"
+
+#include <memory>
+#include <array>
+#include <functional>
+
+namespace nghttp2 {
+
+template <typename T, typename... U>
+typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(U &&... u) {
+  return std::unique_ptr<T>(new T(std::forward<U>(u)...));
+}
+
+template <typename T>
+typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
+make_unique(size_t size) {
+  return std::unique_ptr<T>(new typename std::remove_extent<T>::type[size]());
+}
+
+template <typename T, typename... Rest>
+std::array<T, sizeof...(Rest)+1> make_array(T &&t, Rest &&... rest) {
+  return std::array<T, sizeof...(Rest)+1>{
+      {std::forward<T>(t), std::forward<Rest>(rest)...}};
+}
+
+template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) {
+  return N;
+}
+
+// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our
+// template can take functions returning other than void.
+template <typename F, typename... T> struct Defer {
+  Defer(F &&f, T &&... t)
+      : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {}
+  Defer(Defer &&o) : f(std::move(o.f)) {}
+  ~Defer() { f(); }
+
+  using ResultType = typename std::result_of<
+      typename std::decay<F>::type(typename std::decay<T>::type...)>::type;
+  std::function<ResultType()> f;
+};
+
+template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&... t) {
+  return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...);
+}
+
+} // namespace nghttp2
+
+#endif // TEMPLATE_H
diff --git a/src/timegm.c b/src/timegm.c
new file mode 100644 (file)
index 0000000..bf572b7
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "timegm.h"
+
+#ifndef HAVE_TIMEGM
+
+#include <stdint.h>
+
+/* Counter the number of leap year in the range [0, y). The |y| is the
+   year, including century (e.g., 2012) */
+static int count_leap_year(int y) {
+  y -= 1;
+  return y / 4 - y / 100 + y / 400;
+}
+
+/* Returns nonzero if the |y| is the leap year. The |y| is the year,
+   including century (e.g., 2012) */
+static int is_leap_year(int y) {
+  return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
+}
+
+/* The number of days before ith month begins */
+static int daysum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+/* Based on the algorithm of Python 2.7 calendar.timegm. */
+time_t timegm(struct tm *tm) {
+  int days;
+  int num_leap_year;
+  int64_t t;
+  if (tm->tm_mon > 11) {
+    return -1;
+  }
+  num_leap_year = count_leap_year(tm->tm_year + 1900) - count_leap_year(1970);
+  days = (tm->tm_year - 70) * 365 + num_leap_year + daysum[tm->tm_mon] +
+         tm->tm_mday - 1;
+  if (tm->tm_mon >= 2 && is_leap_year(tm->tm_year + 1900)) {
+    ++days;
+  }
+  t = ((int64_t)days * 24 + tm->tm_hour) * 3600 + tm->tm_min * 60 + tm->tm_sec;
+  if (sizeof(time_t) == 4) {
+    if (t < INT32_MIN || t > INT32_MAX) {
+      return -1;
+    }
+  }
+  return (time_t)t;
+}
+
+#endif /* !HAVE_TIMEGM */
diff --git a/src/timegm.h b/src/timegm.h
new file mode 100644 (file)
index 0000000..529235c
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef TIMEGM_H
+#define TIMEGM_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <time.h>
+
+#ifndef HAVE_TIMEGM
+
+time_t timegm(struct tm *tm);
+
+#endif /* HAVE_TIMEGM */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* TIMEGM_H */
diff --git a/src/util.cc b/src/util.cc
new file mode 100644 (file)
index 0000000..9d133e6
--- /dev/null
@@ -0,0 +1,1047 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "util.h"
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <cmath>
+#include <cerrno>
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+
+#include <nghttp2/nghttp2.h>
+
+#include "timegm.h"
+
+namespace nghttp2 {
+
+namespace util {
+
+const char DEFAULT_STRIP_CHARSET[] = "\r\n\t ";
+
+const char UPPER_XDIGITS[] = "0123456789ABCDEF";
+
+bool isAlpha(const char c) {
+  return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+}
+
+bool isDigit(const char c) { return '0' <= c && c <= '9'; }
+
+bool isHexDigit(const char c) {
+  return isDigit(c) || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
+}
+
+bool inRFC3986UnreservedChars(const char c) {
+  static const char unreserved[] = {'-', '.', '_', '~'};
+  return isAlpha(c) || isDigit(c) ||
+         std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4];
+}
+
+std::string percentEncode(const unsigned char *target, size_t len) {
+  std::string dest;
+  for (size_t i = 0; i < len; ++i) {
+    unsigned char c = target[i];
+
+    if (inRFC3986UnreservedChars(c)) {
+      dest += c;
+    } else {
+      dest += "%";
+      dest += UPPER_XDIGITS[c >> 4];
+      dest += UPPER_XDIGITS[(c & 0x0f)];
+    }
+  }
+  return dest;
+}
+
+std::string percentEncode(const std::string &target) {
+  return percentEncode(reinterpret_cast<const unsigned char *>(target.c_str()),
+                       target.size());
+}
+
+bool in_token(char c) {
+  static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+',
+                               '-', '.', '^', '_', '`', '|',  '~'};
+
+  return isAlpha(c) || isDigit(c) ||
+         std::find(&extra[0], &extra[sizeof(extra)], c) !=
+             &extra[sizeof(extra)];
+}
+
+bool in_attr_char(char c) {
+  static const char bad[] = {'*', '\'', '%'};
+  return util::in_token(c) &&
+         std::find(std::begin(bad), std::end(bad) - 1, c) == std::end(bad) - 1;
+}
+
+std::string percent_encode_token(const std::string &target) {
+  auto len = target.size();
+  std::string dest;
+
+  for (size_t i = 0; i < len; ++i) {
+    unsigned char c = target[i];
+
+    if (c != '%' && in_token(c)) {
+      dest += c;
+    } else {
+      dest += "%";
+      dest += UPPER_XDIGITS[c >> 4];
+      dest += UPPER_XDIGITS[(c & 0x0f)];
+    }
+  }
+  return dest;
+}
+
+std::string percentDecode(std::string::const_iterator first,
+                          std::string::const_iterator last) {
+  std::string result;
+  for (; first != last; ++first) {
+    if (*first == '%') {
+      if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) &&
+          isHexDigit(*(first + 2))) {
+        std::string numstr(first + 1, first + 3);
+        result += strtol(numstr.c_str(), 0, 16);
+        first += 2;
+      } else {
+        result += *first;
+      }
+    } else {
+      result += *first;
+    }
+  }
+  return result;
+}
+
+std::string quote_string(const std::string &target) {
+  auto cnt = std::count(std::begin(target), std::end(target), '"');
+
+  if (cnt == 0) {
+    return target;
+  }
+
+  std::string res;
+  res.reserve(target.size() + cnt);
+
+  for (auto c : target) {
+    if (c == '"') {
+      res += "\\\"";
+    } else {
+      res += c;
+    }
+  }
+
+  return res;
+}
+
+namespace {
+template <typename Iterator>
+Iterator cpydig(Iterator d, uint32_t n, size_t len) {
+  auto p = d + len - 1;
+
+  do {
+    *p-- = (n % 10) + '0';
+    n /= 10;
+  } while (p >= d);
+
+  return d + len;
+}
+} // namespace
+
+namespace {
+const char *MONTH[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+const char *DAY_OF_WEEK[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+} // namespace
+
+std::string http_date(time_t t) {
+  struct tm tms;
+  std::string res;
+
+  if (gmtime_r(&t, &tms) == nullptr) {
+    return res;
+  }
+
+  /* Sat, 27 Sep 2014 06:31:15 GMT */
+  res.resize(29);
+
+  auto p = std::begin(res);
+
+  auto s = DAY_OF_WEEK[tms.tm_wday];
+  p = std::copy_n(s, 3, p);
+  *p++ = ',';
+  *p++ = ' ';
+  p = cpydig(p, tms.tm_mday, 2);
+  *p++ = ' ';
+  s = MONTH[tms.tm_mon];
+  p = std::copy_n(s, 3, p);
+  *p++ = ' ';
+  p = cpydig(p, tms.tm_year + 1900, 4);
+  *p++ = ' ';
+  p = cpydig(p, tms.tm_hour, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_min, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_sec, 2);
+  s = " GMT";
+  p = std::copy_n(s, 4, p);
+
+  return res;
+}
+
+std::string common_log_date(time_t t) {
+  struct tm tms;
+
+  if (localtime_r(&t, &tms) == nullptr) {
+    return "";
+  }
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+  // Format data like this:
+  // 03/Jul/2014:00:19:38 +0900
+  std::string res;
+  res.resize(26);
+
+  auto p = std::begin(res);
+
+  p = cpydig(p, tms.tm_mday, 2);
+  *p++ = '/';
+  auto s = MONTH[tms.tm_mon];
+  p = std::copy_n(s, 3, p);
+  *p++ = '/';
+  p = cpydig(p, tms.tm_year + 1900, 4);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_hour, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_min, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_sec, 2);
+  *p++ = ' ';
+
+  auto gmtoff = tms.tm_gmtoff;
+  if (gmtoff >= 0) {
+    *p++ = '+';
+  } else {
+    *p++ = '-';
+    gmtoff = -gmtoff;
+  }
+
+  p = cpydig(p, gmtoff / 3600, 2);
+  p = cpydig(p, (gmtoff % 3600) / 60, 2);
+
+  return res;
+#else  // !HAVE_STRUCT_TM_TM_GMTOFF
+  char buf[32];
+
+  strftime(buf, sizeof(buf), "%d/%b/%Y:%T %z", &tms);
+
+  return buf;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+}
+
+std::string iso8601_date(int64_t ms) {
+  time_t sec = ms / 1000;
+
+  tm tms;
+  if (localtime_r(&sec, &tms) == nullptr) {
+    return "";
+  }
+
+#ifdef HAVE_STRUCT_TM_TM_GMTOFF
+  // Format data like this:
+  // 2014-11-15T12:58:24.741Z
+  // 2014-11-15T12:58:24.741+09:00
+  std::string res;
+  res.resize(29);
+
+  auto p = std::begin(res);
+
+  p = cpydig(p, tms.tm_year + 1900, 4);
+  *p++ = '-';
+  p = cpydig(p, tms.tm_mon + 1, 2);
+  *p++ = '-';
+  p = cpydig(p, tms.tm_mday, 2);
+  *p++ = 'T';
+  p = cpydig(p, tms.tm_hour, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_min, 2);
+  *p++ = ':';
+  p = cpydig(p, tms.tm_sec, 2);
+  *p++ = '.';
+  p = cpydig(p, ms % 1000, 3);
+
+  auto gmtoff = tms.tm_gmtoff;
+  if (gmtoff == 0) {
+    *p++ = 'Z';
+  } else {
+    if (gmtoff > 0) {
+      *p++ = '+';
+    } else {
+      *p++ = '-';
+      gmtoff = -gmtoff;
+    }
+    p = cpydig(p, gmtoff / 3600, 2);
+    *p++ = ':';
+    p = cpydig(p, (gmtoff % 3600) / 60, 2);
+  }
+
+  res.resize(p - std::begin(res));
+
+  return res;
+#else  // !HAVE_STRUCT_TM_TM_GMTOFF
+  char buf[128];
+
+  auto nwrite = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tms);
+  nwrite += snprintf(&buf[nwrite], sizeof(buf) - nwrite, ".%03d",
+                     static_cast<int>(ms % 1000));
+  auto nzone = strftime(&buf[nwrite], sizeof(buf) - nwrite, "%z", &tms);
+
+  // %z of strftime writes +hhmm or -hhmm not Z, +hh:mm or -hh:mm.  Do
+  // %nothing if nzone is not 5.  we don't know how to cope with this.
+  if (nzone == 5) {
+    if (memcmp(&buf[nwrite], "+0000", 5) == 0) {
+      // 0000 should be Z
+      memcpy(&buf[nwrite], "Z", 2);
+    } else {
+      // Move mm part to right by 1 including terminal \0
+      memmove(&buf[nwrite + 4], &buf[nwrite + 3], 3);
+      // Insert ':' between hh and mm
+      buf[nwrite + 3] = ':';
+    }
+  }
+  return buf;
+#endif // !HAVE_STRUCT_TM_TM_GMTOFF
+}
+
+time_t parse_http_date(const std::string &s) {
+  tm tm;
+  memset(&tm, 0, sizeof(tm));
+  char *r = strptime(s.c_str(), "%a, %d %b %Y %H:%M:%S GMT", &tm);
+  if (r == 0) {
+    return 0;
+  }
+  return timegm(&tm);
+}
+
+bool startsWith(const std::string &a, const std::string &b) {
+  return startsWith(a.begin(), a.end(), b.begin(), b.end());
+}
+
+bool istartsWith(const std::string &a, const std::string &b) {
+  return istartsWith(a.begin(), a.end(), b.begin(), b.end());
+}
+
+namespace {
+void streq_advance(const char **ap, const char **bp) {
+  for (; **ap && **bp && lowcase(**ap) == lowcase(**bp); ++*ap, ++*bp)
+    ;
+}
+} // namespace
+
+bool istartsWith(const char *a, const char *b) {
+  if (!a || !b) {
+    return false;
+  }
+  streq_advance(&a, &b);
+  return !*b;
+}
+
+bool istartsWith(const char *a, size_t n, const char *b) {
+  return istartsWith(a, a + n, b, b + strlen(b));
+}
+
+bool endsWith(const std::string &a, const std::string &b) {
+  return endsWith(a.begin(), a.end(), b.begin(), b.end());
+}
+
+bool strieq(const std::string &a, const std::string &b) {
+  if (a.size() != b.size()) {
+    return false;
+  }
+  for (size_t i = 0; i < a.size(); ++i) {
+    if (lowcase(a[i]) != lowcase(b[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool strieq(const char *a, const char *b) {
+  if (!a || !b) {
+    return false;
+  }
+  for (; *a && *b && lowcase(*a) == lowcase(*b); ++a, ++b)
+    ;
+  return !*a && !*b;
+}
+
+bool strieq(const char *a, const uint8_t *b, size_t bn) {
+  if (!a || !b) {
+    return false;
+  }
+  const uint8_t *blast = b + bn;
+  for (; *a && b != blast && lowcase(*a) == lowcase(*b); ++a, ++b)
+    ;
+  return !*a && b == blast;
+}
+
+bool strieq(const char *a, const char *b, size_t bn) {
+  return strieq(a, reinterpret_cast<const uint8_t *>(b), bn);
+}
+
+int strcompare(const char *a, const uint8_t *b, size_t bn) {
+  assert(a && b);
+  const uint8_t *blast = b + bn;
+  for (; *a && b != blast; ++a, ++b) {
+    if (*a < *b) {
+      return -1;
+    } else if (*a > *b) {
+      return 1;
+    }
+  }
+  if (!*a && b == blast) {
+    return 0;
+  } else if (b == blast) {
+    return 1;
+  } else {
+    return -1;
+  }
+}
+
+bool strifind(const char *a, const char *b) {
+  if (!a || !b) {
+    return false;
+  }
+  for (size_t i = 0; a[i]; ++i) {
+    const char *ap = &a[i], *bp = b;
+    for (; *ap && *bp && lowcase(*ap) == lowcase(*bp); ++ap, ++bp)
+      ;
+    if (!*bp) {
+      return true;
+    }
+  }
+  return false;
+}
+
+char upcase(char c) {
+  if ('a' <= c && c <= 'z') {
+    return c - 'a' + 'A';
+  } else {
+    return c;
+  }
+}
+
+namespace {
+const char LOWER_XDIGITS[] = "0123456789abcdef";
+} // namespace
+
+std::string format_hex(const unsigned char *s, size_t len) {
+  std::string res;
+  res.resize(len * 2);
+
+  for (size_t i = 0; i < len; ++i) {
+    unsigned char c = s[i];
+
+    res[i * 2] = LOWER_XDIGITS[c >> 4];
+    res[i * 2 + 1] = LOWER_XDIGITS[c & 0x0f];
+  }
+  return res;
+}
+
+void to_token68(std::string &base64str) {
+  for (auto i = std::begin(base64str); i != std::end(base64str); ++i) {
+    switch (*i) {
+    case '+':
+      *i = '-';
+      break;
+    case '/':
+      *i = '_';
+      break;
+    case '=':
+      base64str.erase(i, std::end(base64str));
+      return;
+    }
+  }
+  return;
+}
+
+void to_base64(std::string &token68str) {
+  for (auto i = std::begin(token68str); i != std::end(token68str); ++i) {
+    switch (*i) {
+    case '-':
+      *i = '+';
+      break;
+    case '_':
+      *i = '/';
+      break;
+    }
+  }
+  if (token68str.size() & 0x3) {
+    token68str.append(4 - (token68str.size() & 0x3), '=');
+  }
+  return;
+}
+
+void inp_strlower(std::string &s) {
+  for (auto i = std::begin(s); i != std::end(s); ++i) {
+    if ('A' <= *i && *i <= 'Z') {
+      *i = (*i) - 'A' + 'a';
+    }
+  }
+}
+
+namespace {
+// Calculates Damerau–Levenshtein distance between c-string a and b
+// with given costs.  swapcost, subcost, addcost and delcost are cost
+// to swap 2 adjacent characters, substitute characters, add character
+// and delete character respectively.
+int levenshtein(const char *a, int alen, const char *b, int blen, int swapcost,
+                int subcost, int addcost, int delcost) {
+  auto dp = std::vector<std::vector<int>>(3, std::vector<int>(blen + 1));
+  for (int i = 0; i <= blen; ++i) {
+    dp[1][i] = i;
+  }
+  for (int i = 1; i <= alen; ++i) {
+    dp[0][0] = i;
+    for (int j = 1; j <= blen; ++j) {
+      dp[0][j] = dp[1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : subcost);
+      if (i >= 2 && j >= 2 && a[i - 1] != b[j - 1] && a[i - 2] == b[j - 1] &&
+          a[i - 1] == b[j - 2]) {
+        dp[0][j] = std::min(dp[0][j], dp[2][j - 2] + swapcost);
+      }
+      dp[0][j] = std::min(dp[0][j],
+                          std::min(dp[1][j] + delcost, dp[0][j - 1] + addcost));
+    }
+    std::rotate(std::begin(dp), std::begin(dp) + 2, std::end(dp));
+  }
+  return dp[1][blen];
+}
+} // namespace
+
+void show_candidates(const char *unkopt, option *options) {
+  for (; *unkopt == '-'; ++unkopt)
+    ;
+  if (*unkopt == '\0') {
+    return;
+  }
+  auto unkoptend = unkopt;
+  for (; *unkoptend && *unkoptend != '='; ++unkoptend)
+    ;
+  auto unkoptlen = unkoptend - unkopt;
+  if (unkoptlen == 0) {
+    return;
+  }
+  int prefix_match = 0;
+  auto cands = std::vector<std::pair<int, const char *>>();
+  for (size_t i = 0; options[i].name != nullptr; ++i) {
+    auto optnamelen = strlen(options[i].name);
+    // Use cost 0 for prefix match
+    if (istartsWith(options[i].name, options[i].name + optnamelen, unkopt,
+                    unkopt + unkoptlen)) {
+      if (optnamelen == static_cast<size_t>(unkoptlen)) {
+        // Exact match, then we don't show any condidates.
+        return;
+      }
+      ++prefix_match;
+      cands.emplace_back(0, options[i].name);
+      continue;
+    }
+    // Use cost 0 for suffix match, but match at least 3 characters
+    if (unkoptlen >= 3 &&
+        iendsWith(options[i].name, options[i].name + optnamelen, unkopt,
+                  unkopt + unkoptlen)) {
+      cands.emplace_back(0, options[i].name);
+      continue;
+    }
+    // cost values are borrowed from git, help.c.
+    int sim =
+        levenshtein(unkopt, unkoptlen, options[i].name, optnamelen, 0, 2, 1, 3);
+    cands.emplace_back(sim, options[i].name);
+  }
+  if (prefix_match == 1 || cands.empty()) {
+    return;
+  }
+  std::sort(std::begin(cands), std::end(cands));
+  int threshold = cands[0].first;
+  // threshold value is a magic value.
+  if (threshold > 6) {
+    return;
+  }
+  std::cerr << "\nDid you mean:\n";
+  for (auto &item : cands) {
+    if (item.first > threshold) {
+      break;
+    }
+    std::cerr << "\t--" << item.second << "\n";
+  }
+}
+
+bool has_uri_field(const http_parser_url &u, http_parser_url_fields field) {
+  return u.field_set & (1 << field);
+}
+
+bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2,
+             const http_parser_url &u2, http_parser_url_fields field) {
+  if (!has_uri_field(u1, field)) {
+    if (!has_uri_field(u2, field)) {
+      return true;
+    } else {
+      return false;
+    }
+  } else if (!has_uri_field(u2, field)) {
+    return false;
+  }
+  if (u1.field_data[field].len != u2.field_data[field].len) {
+    return false;
+  }
+  return memcmp(uri1 + u1.field_data[field].off,
+                uri2 + u2.field_data[field].off, u1.field_data[field].len) == 0;
+}
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+             http_parser_url_fields field, const char *t) {
+  if (!has_uri_field(u, field)) {
+    if (!t[0]) {
+      return true;
+    } else {
+      return false;
+    }
+  } else if (!t[0]) {
+    return false;
+  }
+  int i, len = u.field_data[field].len;
+  const char *p = uri + u.field_data[field].off;
+  for (i = 0; i < len && t[i] && p[i] == t[i]; ++i)
+    ;
+  return i == len && !t[i];
+}
+
+std::string get_uri_field(const char *uri, const http_parser_url &u,
+                          http_parser_url_fields field) {
+  if (util::has_uri_field(u, field)) {
+    return std::string(uri + u.field_data[field].off, u.field_data[field].len);
+  } else {
+    return "";
+  }
+}
+
+uint16_t get_default_port(const char *uri, const http_parser_url &u) {
+  if (util::fieldeq(uri, u, UF_SCHEMA, "https")) {
+    return 443;
+  } else if (util::fieldeq(uri, u, UF_SCHEMA, "http")) {
+    return 80;
+  } else {
+    return 443;
+  }
+}
+
+bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2,
+            const http_parser_url &u2) {
+  uint16_t port1, port2;
+  port1 =
+      util::has_uri_field(u1, UF_PORT) ? u1.port : get_default_port(uri1, u1);
+  port2 =
+      util::has_uri_field(u2, UF_PORT) ? u2.port : get_default_port(uri2, u2);
+  return port1 == port2;
+}
+
+void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
+                     http_parser_url_fields field) {
+  if (util::has_uri_field(u, field)) {
+    o.write(uri + u.field_data[field].off, u.field_data[field].len);
+  }
+}
+
+bool numeric_host(const char *hostname) {
+  struct addrinfo hints;
+  struct addrinfo *res;
+  memset(&hints, 0, sizeof(hints));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_flags = AI_NUMERICHOST;
+  if (getaddrinfo(hostname, nullptr, &hints, &res)) {
+    return false;
+  }
+  freeaddrinfo(res);
+  return true;
+}
+
+int reopen_log_file(const char *path) {
+#if defined(__ANDROID__) || defined(ANDROID)
+  int fd;
+
+  if (strcmp("/proc/self/fd/1", path) == 0 ||
+      strcmp("/proc/self/fd/2", path) == 0) {
+
+    // We will get permission denied error when O_APPEND is used for
+    // these paths.
+    fd =
+        open(path, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP);
+  } else {
+    fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
+              S_IRUSR | S_IWUSR | S_IRGRP);
+  }
+#elif defined O_CLOEXEC
+
+  auto fd = open(path, O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
+                 S_IRUSR | S_IWUSR | S_IRGRP);
+#else // !O_CLOEXEC
+
+  auto fd =
+      open(path, O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP);
+
+  // We get race condition if execve is called at the same time.
+  if (fd != -1) {
+    make_socket_closeonexec(fd);
+  }
+
+#endif // !O_CLOEXEC
+
+  if (fd == -1) {
+    return -1;
+  }
+
+  return fd;
+}
+
+std::string ascii_dump(const uint8_t *data, size_t len) {
+  std::string res;
+
+  for (size_t i = 0; i < len; ++i) {
+    auto c = data[i];
+
+    if (c >= 0x20 && c < 0x7f) {
+      res += c;
+    } else {
+      res += ".";
+    }
+  }
+
+  return res;
+}
+
+char *get_exec_path(int argc, char **const argv, const char *cwd) {
+  if (argc == 0 || cwd == nullptr) {
+    return nullptr;
+  }
+
+  auto argv0 = argv[0];
+  auto len = strlen(argv0);
+
+  char *path;
+
+  if (argv0[0] == '/') {
+    path = static_cast<char *>(malloc(len + 1));
+    memcpy(path, argv0, len + 1);
+  } else {
+    auto cwdlen = strlen(cwd);
+    path = static_cast<char *>(malloc(len + 1 + cwdlen + 1));
+    memcpy(path, cwd, cwdlen);
+    path[cwdlen] = '/';
+    memcpy(path + cwdlen + 1, argv0, len + 1);
+  }
+
+  return path;
+}
+
+bool check_path(const std::string &path) {
+  // We don't like '\' in path.
+  return !path.empty() && path[0] == '/' &&
+         path.find('\\') == std::string::npos &&
+         path.find("/../") == std::string::npos &&
+         path.find("/./") == std::string::npos &&
+         !util::endsWith(path, "/..") && !util::endsWith(path, "/.");
+}
+
+int64_t to_time64(const timeval &tv) {
+  return tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+bool check_h2_is_selected(const unsigned char *proto, size_t len) {
+  return streq(NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN, proto,
+               len) ||
+         streq(NGHTTP2_H2_16_ID, NGHTTP2_H2_16_ID_LEN, proto, len);
+}
+
+namespace {
+bool select_h2(const unsigned char **out, unsigned char *outlen,
+               const unsigned char *in, unsigned int inlen, const char *key,
+               unsigned int keylen) {
+  for (auto p = in, end = in + inlen; p + keylen <= end; p += *p + 1) {
+    if (memcmp(key, p, keylen) == 0) {
+      *out = p + 1;
+      *outlen = *p;
+      return true;
+    }
+  }
+  return false;
+}
+} // namespace
+
+bool select_h2(const unsigned char **out, unsigned char *outlen,
+               const unsigned char *in, unsigned int inlen) {
+  return select_h2(out, outlen, in, inlen, NGHTTP2_H2_16_ALPN,
+                   NGHTTP2_H2_16_ALPN_LEN) ||
+         select_h2(out, outlen, in, inlen, NGHTTP2_PROTO_ALPN,
+                   NGHTTP2_PROTO_ALPN_LEN);
+}
+
+std::vector<unsigned char> get_default_alpn() {
+  auto res = std::vector<unsigned char>(NGHTTP2_PROTO_ALPN_LEN +
+                                        NGHTTP2_H2_16_ALPN_LEN);
+  auto p = std::begin(res);
+
+  p = std::copy_n(NGHTTP2_H2_16_ALPN, NGHTTP2_H2_16_ALPN_LEN, p);
+  p = std::copy_n(NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN, p);
+
+  return res;
+}
+
+int make_socket_closeonexec(int fd) {
+  int flags;
+  int rv;
+  while ((flags = fcntl(fd, F_GETFD)) == -1 && errno == EINTR)
+    ;
+  while ((rv = fcntl(fd, F_SETFD, flags | FD_CLOEXEC)) == -1 && errno == EINTR)
+    ;
+  return rv;
+}
+
+int make_socket_nonblocking(int fd) {
+  int flags;
+  int rv;
+  while ((flags = fcntl(fd, F_GETFL, 0)) == -1 && errno == EINTR)
+    ;
+  while ((rv = fcntl(fd, F_SETFL, flags | O_NONBLOCK)) == -1 && errno == EINTR)
+    ;
+  return rv;
+}
+
+int make_socket_nodelay(int fd) {
+  int val = 1;
+  if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&val),
+                 sizeof(val)) == -1) {
+    return -1;
+  }
+  return 0;
+}
+
+int create_nonblock_socket(int family) {
+#ifdef SOCK_NONBLOCK
+  auto fd = socket(family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+
+  if (fd == -1) {
+    return -1;
+  }
+#else  // !SOCK_NONBLOCK
+  auto fd = socket(family, SOCK_STREAM, 0);
+
+  if (fd == -1) {
+    return -1;
+  }
+
+  make_socket_nonblocking(fd);
+  make_socket_closeonexec(fd);
+#endif // !SOCK_NONBLOCK
+
+  make_socket_nodelay(fd);
+
+  return fd;
+}
+
+bool check_socket_connected(int fd) {
+  int error;
+  socklen_t len = sizeof(error);
+  if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) {
+    if (error != 0) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ipv6_numeric_addr(const char *host) {
+  uint8_t dst[16];
+  return inet_pton(AF_INET6, host, dst) == 1;
+}
+
+namespace {
+std::pair<int64_t, size_t> parse_uint_digits(const void *ss, size_t len) {
+  const uint8_t *s = static_cast<const uint8_t *>(ss);
+  int64_t n = 0;
+  size_t i;
+  if (len == 0) {
+    return {-1, 0};
+  }
+  constexpr int64_t max = std::numeric_limits<int64_t>::max();
+  for (i = 0; i < len; ++i) {
+    if ('0' <= s[i] && s[i] <= '9') {
+      if (n > max / 10) {
+        return {-1, 0};
+      }
+      n *= 10;
+      if (n > max - (s[i] - '0')) {
+        return {-1, 0};
+      }
+      n += s[i] - '0';
+      continue;
+    }
+    break;
+  }
+  if (i == 0) {
+    return {-1, 0};
+  }
+  return {n, i};
+}
+} // namespace
+
+int64_t parse_uint_with_unit(const char *s) {
+  int64_t n;
+  size_t i;
+  auto len = strlen(s);
+  std::tie(n, i) = parse_uint_digits(s, len);
+  if (n == -1) {
+    return -1;
+  }
+  if (i == len) {
+    return n;
+  }
+  if (i + 1 != len) {
+    return -1;
+  }
+  int mul = 1;
+  switch (s[i]) {
+  case 'K':
+  case 'k':
+    mul = 1 << 10;
+    break;
+  case 'M':
+  case 'm':
+    mul = 1 << 20;
+    break;
+  case 'G':
+  case 'g':
+    mul = 1 << 30;
+    break;
+  default:
+    return -1;
+  }
+  constexpr int64_t max = std::numeric_limits<int64_t>::max();
+  if (n > max / mul) {
+    return -1;
+  }
+  return n * mul;
+}
+
+int64_t parse_uint(const char *s) {
+  return parse_uint(reinterpret_cast<const uint8_t *>(s), strlen(s));
+}
+
+int64_t parse_uint(const std::string &s) {
+  return parse_uint(reinterpret_cast<const uint8_t *>(s.c_str()), s.size());
+}
+
+int64_t parse_uint(const uint8_t *s, size_t len) {
+  int64_t n;
+  size_t i;
+  std::tie(n, i) = parse_uint_digits(s, len);
+  if (n == -1 || i != len) {
+    return -1;
+  }
+  return n;
+}
+
+double parse_duration_with_unit(const char *s) {
+  int64_t n;
+  size_t i;
+  auto len = strlen(s);
+  std::tie(n, i) = parse_uint_digits(s, len);
+  if (n == -1) {
+    goto fail;
+  }
+  if (i == len) {
+    return static_cast<double>(n);
+  }
+  switch (s[i]) {
+  case 'S':
+  case 's':
+    if (i + 1 != len) {
+      goto fail;
+    }
+    return static_cast<double>(n);
+    break;
+  case 'M':
+  case 'm':
+    if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) {
+      goto fail;
+    }
+    return static_cast<double>(n) / 1000.;
+  }
+fail:
+  return std::numeric_limits<double>::infinity();
+}
+
+std::string duration_str(double t) {
+  if (t == 0.) {
+    return "0";
+  }
+  auto frac = static_cast<int64_t>(t * 1000) % 1000;
+  if (frac > 0) {
+    return utos(static_cast<int64_t>(t * 1000)) + "ms";
+  }
+  return utos(static_cast<int64_t>(t)) + "s";
+}
+
+std::string format_duration(const std::chrono::microseconds &u) {
+  const char *unit = "us";
+  int d = 0;
+  auto t = u.count();
+  if (t >= 1000000) {
+    d = 1000000;
+    unit = "s";
+  } else if (t >= 1000) {
+    d = 1000;
+    unit = "ms";
+  } else {
+    return utos(t) + unit;
+  }
+  return dtos(static_cast<double>(t) / d) + unit;
+}
+
+std::string dtos(double n) {
+  auto f = utos(static_cast<int64_t>(round(100. * n)) % 100);
+  return utos(static_cast<int64_t>(n)) + "." + (f.size() == 1 ? "0" : "") + f;
+}
+
+} // namespace util
+
+} // namespace nghttp2
diff --git a/src/util.h b/src/util.h
new file mode 100644 (file)
index 0000000..9e42b64
--- /dev/null
@@ -0,0 +1,534 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef UTIL_H
+#define UTIL_H
+
+#include "nghttp2_config.h"
+
+#include <unistd.h>
+#include <getopt.h>
+
+#include <cmath>
+#include <cstring>
+#include <cassert>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <sstream>
+#include <memory>
+#include <chrono>
+
+#include "http-parser/http_parser.h"
+
+namespace nghttp2 {
+
+// The additional HTTP/2 protocol ALPN protocol identifier we also
+// supports for our applications.  This will be removed once HTTP/2
+// specification is finalized.
+#define NGHTTP2_H2_16_ALPN "\x5h2-16"
+#define NGHTTP2_H2_16_ALPN_LEN (sizeof(NGHTTP2_H2_16_ALPN) - 1)
+#define NGHTTP2_H2_16_ID "h2-16"
+#define NGHTTP2_H2_16_ID_LEN (sizeof(NGHTTP2_H2_16_ID) - 1)
+
+namespace util {
+
+extern const char DEFAULT_STRIP_CHARSET[];
+
+template <typename InputIterator>
+std::pair<InputIterator, InputIterator>
+stripIter(InputIterator first, InputIterator last,
+          const char *chars = DEFAULT_STRIP_CHARSET) {
+  for (; first != last && strchr(chars, *first) != 0; ++first)
+    ;
+  if (first == last) {
+    return std::make_pair(first, last);
+  }
+  InputIterator left = last - 1;
+  for (; left != first && strchr(chars, *left) != 0; --left)
+    ;
+  return std::make_pair(first, left + 1);
+}
+
+template <typename InputIterator, typename OutputIterator>
+OutputIterator splitIter(InputIterator first, InputIterator last,
+                         OutputIterator out, char delim, bool doStrip = false,
+                         bool allowEmpty = false) {
+  for (InputIterator i = first; i != last;) {
+    InputIterator j = std::find(i, last, delim);
+    std::pair<InputIterator, InputIterator> p(i, j);
+    if (doStrip) {
+      p = stripIter(i, j);
+    }
+    if (allowEmpty || p.first != p.second) {
+      *out++ = p;
+    }
+    i = j;
+    if (j != last) {
+      ++i;
+    }
+  }
+  if (allowEmpty && (first == last || *(last - 1) == delim)) {
+    *out++ = std::make_pair(last, last);
+  }
+  return out;
+}
+
+template <typename InputIterator, typename OutputIterator>
+OutputIterator split(InputIterator first, InputIterator last,
+                     OutputIterator out, char delim, bool doStrip = false,
+                     bool allowEmpty = false) {
+  for (InputIterator i = first; i != last;) {
+    InputIterator j = std::find(i, last, delim);
+    std::pair<InputIterator, InputIterator> p(i, j);
+    if (doStrip) {
+      p = stripIter(i, j);
+    }
+    if (allowEmpty || p.first != p.second) {
+      *out++ = std::string(p.first, p.second);
+    }
+    i = j;
+    if (j != last) {
+      ++i;
+    }
+  }
+  if (allowEmpty && (first == last || *(last - 1) == delim)) {
+    *out++ = std::string(last, last);
+  }
+  return out;
+}
+
+template <typename InputIterator, typename DelimiterType>
+std::string strjoin(InputIterator first, InputIterator last,
+                    const DelimiterType &delim) {
+  std::string result;
+  if (first == last) {
+    return result;
+  }
+  InputIterator beforeLast = last - 1;
+  for (; first != beforeLast; ++first) {
+    result += *first;
+    result += delim;
+  }
+  result += *beforeLast;
+  return result;
+}
+
+template <typename InputIterator>
+std::string joinPath(InputIterator first, InputIterator last) {
+  std::vector<std::string> elements;
+  for (; first != last; ++first) {
+    if (*first == "..") {
+      if (!elements.empty()) {
+        elements.pop_back();
+      }
+    } else if (*first == ".") {
+      // do nothing
+    } else {
+      elements.push_back(*first);
+    }
+  }
+  return strjoin(elements.begin(), elements.end(), "/");
+}
+
+bool isAlpha(const char c);
+
+bool isDigit(const char c);
+
+bool isHexDigit(const char c);
+
+bool inRFC3986UnreservedChars(const char c);
+
+// Returns true if |c| is in token (HTTP-p1, Section 3.2.6)
+bool in_token(char c);
+
+bool in_attr_char(char c);
+
+std::string percentEncode(const unsigned char *target, size_t len);
+
+std::string percentEncode(const std::string &target);
+
+std::string percentDecode(std::string::const_iterator first,
+                          std::string::const_iterator last);
+
+// Percent encode |target| if character is not in token or '%'.
+std::string percent_encode_token(const std::string &target);
+
+// Returns quotedString version of |target|.  Currently, this function
+// just replace '"' with '\"'.
+std::string quote_string(const std::string &target);
+
+std::string format_hex(const unsigned char *s, size_t len);
+
+std::string http_date(time_t t);
+
+// Returns given time |t| from epoch in Common Log format (e.g.,
+// 03/Jul/2014:00:19:38 +0900)
+std::string common_log_date(time_t t);
+
+// Returns given millisecond |ms| from epoch in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z)
+std::string iso8601_date(int64_t ms);
+
+time_t parse_http_date(const std::string &s);
+
+template <typename InputIterator1, typename InputIterator2>
+bool startsWith(InputIterator1 first1, InputIterator1 last1,
+                InputIterator2 first2, InputIterator2 last2) {
+  if (last1 - first1 < last2 - first2) {
+    return false;
+  }
+  return std::equal(first2, last2, first1);
+}
+
+bool startsWith(const std::string &a, const std::string &b);
+
+struct CaseCmp {
+  bool operator()(char lhs, char rhs) const {
+    if ('A' <= lhs && lhs <= 'Z') {
+      lhs += 'a' - 'A';
+    }
+    if ('A' <= rhs && rhs <= 'Z') {
+      rhs += 'a' - 'A';
+    }
+    return lhs == rhs;
+  }
+};
+
+template <typename InputIterator1, typename InputIterator2>
+bool istartsWith(InputIterator1 first1, InputIterator1 last1,
+                 InputIterator2 first2, InputIterator2 last2) {
+  if (last1 - first1 < last2 - first2) {
+    return false;
+  }
+  return std::equal(first2, last2, first1, CaseCmp());
+}
+
+bool istartsWith(const std::string &a, const std::string &b);
+bool istartsWith(const char *a, const char *b);
+bool istartsWith(const char *a, size_t n, const char *b);
+
+template <typename InputIterator1, typename InputIterator2>
+bool endsWith(InputIterator1 first1, InputIterator1 last1,
+              InputIterator2 first2, InputIterator2 last2) {
+  if (last1 - first1 < last2 - first2) {
+    return false;
+  }
+  return std::equal(first2, last2, last1 - (last2 - first2));
+}
+
+template <typename InputIterator1, typename InputIterator2>
+bool iendsWith(InputIterator1 first1, InputIterator1 last1,
+               InputIterator2 first2, InputIterator2 last2) {
+  if (last1 - first1 < last2 - first2) {
+    return false;
+  }
+  return std::equal(first2, last2, last1 - (last2 - first2), CaseCmp());
+}
+
+bool endsWith(const std::string &a, const std::string &b);
+
+int strcompare(const char *a, const uint8_t *b, size_t n);
+
+bool strieq(const std::string &a, const std::string &b);
+
+bool strieq(const char *a, const char *b);
+
+bool strieq(const char *a, const uint8_t *b, size_t n);
+
+bool strieq(const char *a, const char *b, size_t n);
+
+template <typename A, typename B>
+bool streq(const A *a, const B *b, size_t bn) {
+  if (!a || !b) {
+    return false;
+  }
+  auto blast = b + bn;
+  for (; *a && b != blast && *a == *b; ++a, ++b)
+    ;
+  return !*a && b == blast;
+}
+
+template <typename A, typename B>
+bool streq(const A *a, size_t alen, const B *b, size_t blen) {
+  if (alen != blen) {
+    return false;
+  }
+  return memcmp(a, b, alen) == 0;
+}
+
+bool strifind(const char *a, const char *b);
+
+char upcase(char c);
+
+char lowcase(char c);
+
+inline char lowcase(char c) {
+  static unsigned char tbl[] = {
+      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
+      15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
+      30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,
+      45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
+      60,  61,  62,  63,  64,  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+      'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
+      'z', 91,  92,  93,  94,  95,  96,  97,  98,  99,  100, 101, 102, 103, 104,
+      105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+      120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
+      135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+      150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
+      165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+      180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
+      195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+      210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
+      225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+      240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
+      255,
+  };
+  return tbl[static_cast<unsigned char>(c)];
+}
+
+// Lowercase |s| in place.
+void inp_strlower(std::string &s);
+
+// Returns string representation of |n| with 2 fractional digits.
+std::string dtos(double n);
+
+template <typename T> std::string utos(T n) {
+  std::string res;
+  if (n == 0) {
+    res = "0";
+    return res;
+  }
+  int i = 0;
+  T t = n;
+  for (; t; t /= 10, ++i)
+    ;
+  res.resize(i);
+  --i;
+  for (; n; --i, n /= 10) {
+    res[i] = (n % 10) + '0';
+  }
+  return res;
+}
+
+template <typename T> std::string utos_with_unit(T n) {
+  char u = 0;
+  if (n >= (1 << 30)) {
+    u = 'G';
+    n /= (1 << 30);
+  } else if (n >= (1 << 20)) {
+    u = 'M';
+    n /= (1 << 20);
+  } else if (n >= (1 << 10)) {
+    u = 'K';
+    n /= (1 << 10);
+  }
+  if (u == 0) {
+    return utos(n);
+  }
+  return utos(n) + u;
+}
+
+// Like utos_with_unit(), but 2 digits fraction part is followed.
+template <typename T> std::string utos_with_funit(T n) {
+  char u = 0;
+  int b = 0;
+  if (n >= (1 << 30)) {
+    u = 'G';
+    b = 30;
+  } else if (n >= (1 << 20)) {
+    u = 'M';
+    b = 20;
+  } else if (n >= (1 << 10)) {
+    u = 'K';
+    b = 10;
+  }
+  if (b == 0) {
+    return utos(n);
+  }
+  return dtos(static_cast<double>(n) / (1 << b)) + u;
+}
+
+extern const char UPPER_XDIGITS[];
+
+template <typename T> std::string utox(T n) {
+  std::string res;
+  if (n == 0) {
+    res = "0";
+    return res;
+  }
+  int i = 0;
+  T t = n;
+  for (; t; t /= 16, ++i)
+    ;
+  res.resize(i);
+  --i;
+  for (; n; --i, n /= 16) {
+    res[i] = UPPER_XDIGITS[(n & 0x0f)];
+  }
+  return res;
+}
+
+void to_token68(std::string &base64str);
+void to_base64(std::string &token68str);
+
+void show_candidates(const char *unkopt, option *options);
+
+bool has_uri_field(const http_parser_url &u, http_parser_url_fields field);
+
+bool fieldeq(const char *uri1, const http_parser_url &u1, const char *uri2,
+             const http_parser_url &u2, http_parser_url_fields field);
+
+bool fieldeq(const char *uri, const http_parser_url &u,
+             http_parser_url_fields field, const char *t);
+
+std::string get_uri_field(const char *uri, const http_parser_url &u,
+                          http_parser_url_fields field);
+
+uint16_t get_default_port(const char *uri, const http_parser_url &u);
+
+bool porteq(const char *uri1, const http_parser_url &u1, const char *uri2,
+            const http_parser_url &u2);
+
+void write_uri_field(std::ostream &o, const char *uri, const http_parser_url &u,
+                     http_parser_url_fields field);
+
+bool numeric_host(const char *hostname);
+
+// Opens |path| with O_APPEND enabled.  If file does not exist, it is
+// created first.  This function returns file descriptor referring the
+// opened file if it succeeds, or -1.
+int reopen_log_file(const char *path);
+
+// Returns ASCII dump of |data| of length |len|.  Only ASCII printable
+// characters are preserved.  Other characters are replaced with ".".
+std::string ascii_dump(const uint8_t *data, size_t len);
+
+// Returns absolute path of executable path.  If argc == 0 or |cwd| is
+// nullptr, this function returns nullptr.  If argv[0] starts with
+// '/', this function returns argv[0].  Oterwise return cwd + "/" +
+// argv[0].  If non-null is returned, it is NULL-terminated string and
+// dynamically allocated by malloc.  The caller is responsible to free
+// it.
+char *get_exec_path(int argc, char **const argv, const char *cwd);
+
+// Validates path so that it does not contain directory traversal
+// vector.  Returns true if path is safe.  The |path| must start with
+// "/" otherwise returns false.  This function should be called after
+// percent-decode was performed.
+bool check_path(const std::string &path);
+
+// Returns the |tv| value as 64 bit integer using a microsecond as an
+// unit.
+int64_t to_time64(const timeval &tv);
+
+// Returns true if ALPN ID |proto| of length |len| is supported HTTP/2
+// protocol identifier.
+bool check_h2_is_selected(const unsigned char *alpn, size_t len);
+
+// Selects h2 protocol ALPN ID if one of supported h2 versions are
+// present in |in| of length inlen.  Returns true if h2 version is
+// selected.
+bool select_h2(const unsigned char **out, unsigned char *outlen,
+               const unsigned char *in, unsigned int inlen);
+
+// Returns default ALPN protocol list, which only contains supported
+// HTTP/2 protocol identifier.
+std::vector<unsigned char> get_default_alpn();
+
+// Returns given time |tp| in Common Log format (e.g.,
+// 03/Jul/2014:00:19:38 +0900)
+// Expected type of |tp| is std::chrono::timepoint
+template <typename T> std::string format_common_log(const T &tp) {
+  auto t =
+      std::chrono::duration_cast<std::chrono::seconds>(tp.time_since_epoch());
+  return common_log_date(t.count());
+}
+
+// Returns given time |tp| in ISO 8601 format (e.g.,
+// 2014-11-15T12:58:24.741Z)
+// Expected type of |tp| is std::chrono::timepoint
+template <typename T> std::string format_iso8601(const T &tp) {
+  auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
+      tp.time_since_epoch());
+  return iso8601_date(t.count());
+}
+
+// Return the system precision of the template parameter |Clock| as
+// a nanosecond value of type |Rep|
+template <typename Clock, typename Rep> Rep clock_precision() {
+  std::chrono::duration<Rep, std::nano> duration = typename Clock::duration(1);
+
+  return duration.count();
+}
+
+int make_socket_closeonexec(int fd);
+int make_socket_nonblocking(int fd);
+int make_socket_nodelay(int fd);
+
+int create_nonblock_socket(int family);
+
+bool check_socket_connected(int fd);
+
+// Returns true if |host| is IPv6 numeric address (e.g., ::1)
+bool ipv6_numeric_addr(const char *host);
+
+// Parses NULL terminated string |s| as unsigned integer and returns
+// the parsed integer.  Additionally, if |s| ends with 'k', 'm', 'g'
+// and its upper case characters, multiply the integer by 1024, 1024 *
+// 1024 and 1024 * 1024 respectively.  If there is an error, returns
+// -1.
+int64_t parse_uint_with_unit(const char *s);
+
+// Parses NULL terminated string |s| as unsigned integer and returns
+// the parsed integer.  If there is an error, returns -1.
+int64_t parse_uint(const char *s);
+int64_t parse_uint(const uint8_t *s, size_t len);
+int64_t parse_uint(const std::string &s);
+
+// Parses NULL terminated string |s| as unsigned integer and returns
+// the parsed integer casted to double.  If |s| ends with "s", the
+// parsed value's unit is a second.  If |s| ends with "ms", the unit
+// is millisecond.  If none of them are given, the unit is second.
+// This function returns std::numeric_limits<double>::infinity() if
+// error occurs.
+double parse_duration_with_unit(const char *s);
+
+// Returns string representation of time duration |t|.  If t has
+// fractional part (at least more than or equal to 1e-3), |t| is
+// multiplied by 1000 and the unit "ms" is appended.  Otherwise, |t|
+// is left as is and "s" is appended.
+std::string duration_str(double t);
+
+// Returns string representation of time duration |t|.  It appends
+// unit after the formatting.  The available units are s, ms and us.
+// The unit which is equal to or less than |t| is used and 2
+// fractional digits follow.
+std::string format_duration(const std::chrono::microseconds &u);
+
+} // namespace util
+
+} // namespace nghttp2
+
+#endif // UTIL_H
diff --git a/src/util_test.cc b/src/util_test.cc
new file mode 100644 (file)
index 0000000..f0871d6
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "util_test.h"
+
+#include <cstring>
+#include <iostream>
+
+#include <CUnit/CUnit.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "util.h"
+
+using namespace nghttp2;
+
+namespace shrpx {
+
+void test_util_streq(void) {
+  CU_ASSERT(util::streq("alpha", (const uint8_t *)"alpha", 5));
+  CU_ASSERT(util::streq("alpha", (const uint8_t *)"alphabravo", 5));
+  CU_ASSERT(!util::streq("alpha", (const uint8_t *)"alphabravo", 6));
+  CU_ASSERT(!util::streq("alphabravo", (const uint8_t *)"alpha", 5));
+  CU_ASSERT(!util::streq("alpha", (const uint8_t *)"alphA", 5));
+  CU_ASSERT(!util::streq("", (const uint8_t *)"a", 1));
+  CU_ASSERT(util::streq("", (const uint8_t *)"", 0));
+  CU_ASSERT(!util::streq("alpha", (const uint8_t *)"", 0));
+
+  CU_ASSERT(
+      util::streq((const uint8_t *)"alpha", 5, (const uint8_t *)"alpha", 5));
+  CU_ASSERT(
+      !util::streq((const uint8_t *)"alpha", 4, (const uint8_t *)"alpha", 5));
+  CU_ASSERT(
+      !util::streq((const uint8_t *)"alpha", 5, (const uint8_t *)"alpha", 4));
+  CU_ASSERT(
+      !util::streq((const uint8_t *)"alpha", 5, (const uint8_t *)"alphA", 5));
+  char *a = nullptr;
+  char *b = nullptr;
+  CU_ASSERT(util::streq(a, 0, b, 0));
+}
+
+void test_util_strieq(void) {
+  CU_ASSERT(util::strieq(std::string("alpha"), std::string("alpha")));
+  CU_ASSERT(util::strieq(std::string("alpha"), std::string("AlPhA")));
+  CU_ASSERT(util::strieq(std::string(), std::string()));
+  CU_ASSERT(!util::strieq(std::string("alpha"), std::string("AlPhA ")));
+  CU_ASSERT(!util::strieq(std::string(), std::string("AlPhA ")));
+}
+
+void test_util_inp_strlower(void) {
+  std::string a("alPha");
+  util::inp_strlower(a);
+  CU_ASSERT("alpha" == a);
+
+  a = "ALPHA123BRAVO";
+  util::inp_strlower(a);
+  CU_ASSERT("alpha123bravo" == a);
+
+  a = "";
+  util::inp_strlower(a);
+  CU_ASSERT("" == a);
+}
+
+void test_util_to_base64(void) {
+  std::string x = "AAA--B_";
+  util::to_base64(x);
+  CU_ASSERT("AAA++B/=" == x);
+
+  x = "AAA--B_B";
+  util::to_base64(x);
+  CU_ASSERT("AAA++B/B" == x);
+}
+
+void test_util_percent_encode_token(void) {
+  CU_ASSERT("h2" == util::percent_encode_token("h2"));
+  CU_ASSERT("h3~" == util::percent_encode_token("h3~"));
+  CU_ASSERT("100%25" == util::percent_encode_token("100%"));
+  CU_ASSERT("http%202" == util::percent_encode_token("http 2"));
+}
+
+void test_util_quote_string(void) {
+  CU_ASSERT("alpha" == util::quote_string("alpha"));
+  CU_ASSERT("" == util::quote_string(""));
+  CU_ASSERT("\\\"alpha\\\"" == util::quote_string("\"alpha\""));
+}
+
+void test_util_utox(void) {
+  CU_ASSERT("0" == util::utox(0));
+  CU_ASSERT("1" == util::utox(1));
+  CU_ASSERT("F" == util::utox(15));
+  CU_ASSERT("10" == util::utox(16));
+  CU_ASSERT("3B9ACA07" == util::utox(1000000007));
+  CU_ASSERT("100000000" == util::utox(1LL << 32));
+}
+
+void test_util_http_date(void) {
+  CU_ASSERT("Thu, 01 Jan 1970 00:00:00 GMT" == util::http_date(0));
+  CU_ASSERT("Wed, 29 Feb 2012 09:15:16 GMT" == util::http_date(1330506916));
+}
+
+void test_util_select_h2(void) {
+  const unsigned char *out = NULL;
+  unsigned char outlen = 0;
+
+  // Check single entry and select it.
+  const unsigned char t1[] = "\x5h2-14";
+  CU_ASSERT(util::select_h2(&out, &outlen, t1, sizeof(t1) - 1));
+  CU_ASSERT(
+      memcmp(NGHTTP2_PROTO_VERSION_ID, out, NGHTTP2_PROTO_VERSION_ID_LEN) == 0);
+  CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen);
+
+  out = NULL;
+  outlen = 0;
+
+  // Check the case where id is correct but length is invalid and too
+  // long.
+  const unsigned char t2[] = "\x6h2-14";
+  CU_ASSERT(!util::select_h2(&out, &outlen, t2, sizeof(t2) - 1));
+
+  // Check the case where h2-14 is located after bogus ID.
+  const unsigned char t3[] = "\x2h3\x5h2-14";
+  CU_ASSERT(util::select_h2(&out, &outlen, t3, sizeof(t3) - 1));
+  CU_ASSERT(
+      memcmp(NGHTTP2_PROTO_VERSION_ID, out, NGHTTP2_PROTO_VERSION_ID_LEN) == 0);
+  CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen);
+
+  out = NULL;
+  outlen = 0;
+
+  // Check the case that last entry's length is invalid and too long.
+  const unsigned char t4[] = "\x2h3\x6h2-14";
+  CU_ASSERT(!util::select_h2(&out, &outlen, t4, sizeof(t4) - 1));
+
+  // Check the case that all entries are not supported.
+  const unsigned char t5[] = "\x2h3\x2h4";
+  CU_ASSERT(!util::select_h2(&out, &outlen, t5, sizeof(t5) - 1));
+
+  // Check the case where 2 values are eligible, but last one is
+  // picked up because it has precedence over the other.
+  const unsigned char t6[] = "\x5h2-14\x5h2-16";
+  CU_ASSERT(util::select_h2(&out, &outlen, t6, sizeof(t6) - 1));
+  CU_ASSERT(memcmp(NGHTTP2_H2_16_ID, out, NGHTTP2_H2_16_ID_LEN) == 0);
+  CU_ASSERT(NGHTTP2_H2_16_ID_LEN == outlen);
+}
+
+void test_util_ipv6_numeric_addr(void) {
+  CU_ASSERT(util::ipv6_numeric_addr("::1"));
+  CU_ASSERT(util::ipv6_numeric_addr("2001:0db8:85a3:0042:1000:8a2e:0370:7334"));
+  // IPv4
+  CU_ASSERT(!util::ipv6_numeric_addr("127.0.0.1"));
+  // not numeric address
+  CU_ASSERT(!util::ipv6_numeric_addr("localhost"));
+}
+
+void test_util_utos_with_unit(void) {
+  CU_ASSERT("0" == util::utos_with_unit(0));
+  CU_ASSERT("1023" == util::utos_with_unit(1023));
+  CU_ASSERT("1K" == util::utos_with_unit(1024));
+  CU_ASSERT("1K" == util::utos_with_unit(1025));
+  CU_ASSERT("1M" == util::utos_with_unit(1 << 20));
+  CU_ASSERT("1G" == util::utos_with_unit(1 << 30));
+  CU_ASSERT("1024G" == util::utos_with_unit(1LL << 40));
+}
+
+void test_util_utos_with_funit(void) {
+  CU_ASSERT("0" == util::utos_with_funit(0));
+  CU_ASSERT("1023" == util::utos_with_funit(1023));
+  CU_ASSERT("1.00K" == util::utos_with_funit(1024));
+  CU_ASSERT("1.00K" == util::utos_with_funit(1025));
+  CU_ASSERT("1.09K" == util::utos_with_funit(1119));
+  CU_ASSERT("1.27K" == util::utos_with_funit(1300));
+  CU_ASSERT("1.00M" == util::utos_with_funit(1 << 20));
+  CU_ASSERT("1.18M" == util::utos_with_funit(1234567));
+  CU_ASSERT("1.00G" == util::utos_with_funit(1 << 30));
+  CU_ASSERT("4492450797.23G" == util::utos_with_funit(4823732313248234343LL));
+  CU_ASSERT("1024.00G" == util::utos_with_funit(1LL << 40));
+}
+
+void test_util_parse_uint_with_unit(void) {
+  CU_ASSERT(0 == util::parse_uint_with_unit("0"));
+  CU_ASSERT(1023 == util::parse_uint_with_unit("1023"));
+  CU_ASSERT(1024 == util::parse_uint_with_unit("1k"));
+  CU_ASSERT(2048 == util::parse_uint_with_unit("2K"));
+  CU_ASSERT(1 << 20 == util::parse_uint_with_unit("1m"));
+  CU_ASSERT(1 << 21 == util::parse_uint_with_unit("2M"));
+  CU_ASSERT(1 << 30 == util::parse_uint_with_unit("1g"));
+  CU_ASSERT(1LL << 31 == util::parse_uint_with_unit("2G"));
+  CU_ASSERT(9223372036854775807LL ==
+            util::parse_uint_with_unit("9223372036854775807"));
+  // check overflow case
+  CU_ASSERT(-1 == util::parse_uint_with_unit("9223372036854775808"));
+  CU_ASSERT(-1 == util::parse_uint_with_unit("10000000000000000000"));
+  CU_ASSERT(-1 == util::parse_uint_with_unit("9223372036854775807G"));
+  // bad characters
+  CU_ASSERT(-1 == util::parse_uint_with_unit("1.1"));
+  CU_ASSERT(-1 == util::parse_uint_with_unit("1a"));
+  CU_ASSERT(-1 == util::parse_uint_with_unit("a1"));
+  CU_ASSERT(-1 == util::parse_uint_with_unit("1T"));
+  CU_ASSERT(-1 == util::parse_uint_with_unit(""));
+}
+
+void test_util_parse_uint(void) {
+  CU_ASSERT(0 == util::parse_uint("0"));
+  CU_ASSERT(1023 == util::parse_uint("1023"));
+  CU_ASSERT(-1 == util::parse_uint("1k"));
+  CU_ASSERT(9223372036854775807LL == util::parse_uint("9223372036854775807"));
+  // check overflow case
+  CU_ASSERT(-1 == util::parse_uint("9223372036854775808"));
+  CU_ASSERT(-1 == util::parse_uint("10000000000000000000"));
+  // bad characters
+  CU_ASSERT(-1 == util::parse_uint("1.1"));
+  CU_ASSERT(-1 == util::parse_uint("1a"));
+  CU_ASSERT(-1 == util::parse_uint("a1"));
+  CU_ASSERT(-1 == util::parse_uint("1T"));
+  CU_ASSERT(-1 == util::parse_uint(""));
+}
+
+void test_util_parse_duration_with_unit(void) {
+  CU_ASSERT(0. == util::parse_duration_with_unit("0"));
+  CU_ASSERT(123. == util::parse_duration_with_unit("123"));
+  CU_ASSERT(123. == util::parse_duration_with_unit("123s"));
+  CU_ASSERT(0.500 == util::parse_duration_with_unit("500ms"));
+  CU_ASSERT(123. == util::parse_duration_with_unit("123S"));
+  CU_ASSERT(0.500 == util::parse_duration_with_unit("500MS"));
+
+  auto err = std::numeric_limits<double>::infinity();
+  // check overflow case
+  CU_ASSERT(err == util::parse_duration_with_unit("9223372036854775808"));
+  // bad characters
+  CU_ASSERT(err == util::parse_duration_with_unit("0u"));
+  CU_ASSERT(err == util::parse_duration_with_unit("0xs"));
+  CU_ASSERT(err == util::parse_duration_with_unit("0mt"));
+  CU_ASSERT(err == util::parse_duration_with_unit("0mss"));
+  CU_ASSERT(err == util::parse_duration_with_unit("s"));
+  CU_ASSERT(err == util::parse_duration_with_unit("ms"));
+}
+
+void test_util_duration_str(void) {
+  CU_ASSERT("0" == util::duration_str(0.));
+  CU_ASSERT("1s" == util::duration_str(1.));
+  CU_ASSERT("500ms" == util::duration_str(0.5));
+  CU_ASSERT("1500ms" == util::duration_str(1.5));
+}
+
+void test_util_format_duration(void) {
+  CU_ASSERT("0us" == util::format_duration(std::chrono::microseconds(0)));
+  CU_ASSERT("999us" == util::format_duration(std::chrono::microseconds(999)));
+  CU_ASSERT("1.00ms" == util::format_duration(std::chrono::microseconds(1000)));
+  CU_ASSERT("1.09ms" == util::format_duration(std::chrono::microseconds(1090)));
+  CU_ASSERT("1.01ms" == util::format_duration(std::chrono::microseconds(1009)));
+  CU_ASSERT("999.99ms" ==
+            util::format_duration(std::chrono::microseconds(999990)));
+  CU_ASSERT("1.00s" ==
+            util::format_duration(std::chrono::microseconds(1000000)));
+  CU_ASSERT("1.05s" ==
+            util::format_duration(std::chrono::microseconds(1050000)));
+}
+
+} // namespace shrpx
diff --git a/src/util_test.h b/src/util_test.h
new file mode 100644 (file)
index 0000000..511ef13
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef UTIL_TEST_H
+#define UTIL_TEST_H
+
+namespace shrpx {
+
+void test_util_streq(void);
+void test_util_strieq(void);
+void test_util_inp_strlower(void);
+void test_util_to_base64(void);
+void test_util_percent_encode_token(void);
+void test_util_quote_string(void);
+void test_util_utox(void);
+void test_util_http_date(void);
+void test_util_select_h2(void);
+void test_util_ipv6_numeric_addr(void);
+void test_util_utos_with_unit(void);
+void test_util_utos_with_funit(void);
+void test_util_parse_uint_with_unit(void);
+void test_util_parse_uint(void);
+void test_util_parse_duration_with_unit(void);
+void test_util_duration_str(void);
+void test_util_format_duration(void);
+
+} // namespace shrpx
+
+#endif // UTIL_TEST_H
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644 (file)
index 0000000..4b24e3c
--- /dev/null
@@ -0,0 +1,7 @@
+test-suite.log
+failmalloc
+failmalloc.log
+failmalloc.trs
+main
+main.log
+main.trs
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644 (file)
index 0000000..4ef17bb
--- /dev/null
@@ -0,0 +1,82 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+SUBDIRS = testdata
+
+if HAVE_CUNIT
+
+check_PROGRAMS = main
+
+if ENABLE_FAILMALLOC
+check_PROGRAMS += failmalloc
+endif # ENABLE_FAILMALLOC
+
+OBJECTS = main.c nghttp2_pq_test.c nghttp2_map_test.c nghttp2_queue_test.c \
+       nghttp2_test_helper.c \
+       nghttp2_frame_test.c \
+       nghttp2_stream_test.c \
+       nghttp2_session_test.c \
+       nghttp2_hd_test.c \
+       nghttp2_npn_test.c \
+       nghttp2_helper_test.c \
+       nghttp2_buf_test.c
+
+HFILES = nghttp2_pq_test.h nghttp2_map_test.h nghttp2_queue_test.h \
+       nghttp2_session_test.h \
+       nghttp2_frame_test.h nghttp2_stream_test.h nghttp2_hd_test.h \
+       nghttp2_npn_test.h nghttp2_helper_test.h \
+       nghttp2_test_helper.h \
+       nghttp2_buf_test.h
+
+main_SOURCES = $(HFILES) $(OBJECTS)
+
+main_LDADD = ${top_builddir}/lib/libnghttp2.la @CUNIT_LIBS@ @TESTLDADD@
+main_LDFLAGS = -static
+
+if ENABLE_FAILMALLOC
+failmalloc_SOURCES = failmalloc.c failmalloc_test.c failmalloc_test.h \
+       malloc_wrapper.c malloc_wrapper.h \
+       nghttp2_test_helper.c nghttp2_test_helper.h
+failmalloc_LDADD = $(main_LDADD)
+failmalloc_LDFLAGS = $(main_LDFLAGS)
+endif # ENABLE_FAILMALLOC
+
+AM_CFLAGS = $(WARNCFLAGS) \
+       -I${top_srcdir}/lib \
+       -I${top_srcdir}/lib/includes \
+       -I${top_builddir}/lib/includes \
+       @CUNIT_CFLAGS@ @DEFS@
+
+TESTS = main
+
+if ENABLE_FAILMALLOC
+TESTS += failmalloc
+endif # ENABLE_FAILMALLOC
+
+if ENABLE_APP
+
+# EXTRA_DIST = end_to_end.py
+# TESTS += end_to_end.py
+
+endif # ENABLE_APP
+
+endif # HAVE_CUNIT
diff --git a/tests/end_to_end.py b/tests/end_to_end.py
new file mode 100755 (executable)
index 0000000..cfb3cdd
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+"""End to end tests for the utility programs.
+
+This test assumes the utilities inside src directory have already been
+built.
+
+At the moment top_buiddir is not in the environment, but top_builddir would be
+more reliable than '..', so it's worth trying to pull it from the environment.
+"""
+
+__author__ = 'Jim Morrison <jim@twist.com>'
+
+
+import os
+import subprocess
+import time
+import unittest
+
+
+_PORT = 9893
+
+
+def _run_server(port, args):
+  srcdir = os.environ.get('srcdir', '.')
+  testdata = '%s/testdata' % srcdir
+  top_builddir = os.environ.get('top_builddir', '..')
+  base_args = ['%s/src/spdyd' % top_builddir, '-d', testdata]
+  if args:
+    base_args.extend(args)
+  base_args.extend([str(port), '%s/privkey.pem' % testdata,
+                    '%s/cacert.pem' % testdata])
+  return subprocess.Popen(base_args)
+
+def _check_server_up(port):
+  # Check this check for now.
+  time.sleep(1)
+
+def _kill_server(server):
+  while server.returncode is None:
+    server.terminate()
+    time.sleep(1)
+    server.poll()
+
+
+class EndToEndSpdyTests(unittest.TestCase):
+  @classmethod
+  def setUpClass(cls):
+    cls.setUpServer([])
+
+  @classmethod
+  def setUpServer(cls, args):
+    cls.server = _run_server(_PORT, args)
+    _check_server_up(_PORT)
+
+  @classmethod
+  def tearDownClass(cls):
+    _kill_server(cls.server)
+
+  def setUp(self):
+    build_dir = os.environ.get('top_builddir', '..')
+    self.client = '%s/src/spdycat' % build_dir
+    self.stdout = 'No output'
+
+  def call(self, path, args):
+     full_args = [self.client,'http://localhost:%d%s' % (_PORT, path)] + args
+     p = subprocess.Popen(full_args, stdout=subprocess.PIPE,
+                          stdin=subprocess.PIPE)
+     self.stdout, self.stderr = p.communicate()
+     return p.returncode
+
+
+class EndToEndSpdy2Tests(EndToEndSpdyTests):
+  def testSimpleRequest(self):
+    self.assertEquals(0, self.call('/', []))
+
+  def testSimpleRequestSpdy3(self):
+    self.assertEquals(0, self.call('/', ['-v', '-3']))
+    self.assertIn('NPN selected the protocol: spdy/3', self.stdout)
+
+  def testFailedRequests(self):
+    self.assertEquals(
+        2, self.call('/', ['https://localhost:25/', 'http://localhost:79']))
+
+  def testOneFailedRequest(self):
+    self.assertEquals(1, subprocess.call([self.client, 'http://localhost:2/']))
+
+  def testOneTimedOutRequest(self):
+    self.assertEquals(1, self.call('/?spdyd_do_not_respond_to_req=yes',
+                                   ['--timeout=2']))
+    self.assertEquals(0, self.call('/', ['--timeout=20']))
+
+
+class EndToEndSpdy3Tests(EndToEndSpdyTests):
+  @classmethod
+  def setUpClass(cls):
+    cls.setUpServer(['-3'])
+
+  def testSimpleRequest(self):
+    self.assertEquals(0, self.call('/', ['-v']))
+    self.assertIn('NPN selected the protocol: spdy/3', self.stdout)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tests/failmalloc.c b/tests/failmalloc.c
new file mode 100644 (file)
index 0000000..8c9f16d
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <string.h>
+#include <CUnit/Basic.h>
+/* include test cases' include files here */
+
+#include "failmalloc_test.h"
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(int argc _U_, char *argv[] _U_) {
+  CU_pSuite pSuite = NULL;
+  unsigned int num_tests_failed;
+
+  /* initialize the CUnit test registry */
+  if (CUE_SUCCESS != CU_initialize_registry())
+    return CU_get_error();
+
+  /* add a suite to the registry */
+  pSuite = CU_add_suite("libnghttp2_TestSuite", init_suite1, clean_suite1);
+  if (NULL == pSuite) {
+    CU_cleanup_registry();
+    return CU_get_error();
+  }
+
+  /* add the tests to the suite */
+  if (!CU_add_test(pSuite, "failmalloc_session_send",
+                   test_nghttp2_session_send) ||
+      !CU_add_test(pSuite, "failmalloc_session_recv",
+                   test_nghttp2_session_recv) ||
+      !CU_add_test(pSuite, "failmalloc_frame", test_nghttp2_frame) ||
+      !CU_add_test(pSuite, "failmalloc_hd", test_nghttp2_hd)) {
+    CU_cleanup_registry();
+    return CU_get_error();
+  }
+
+  /* Run all tests using the CUnit Basic interface */
+  CU_basic_set_mode(CU_BRM_VERBOSE);
+  CU_basic_run_tests();
+  num_tests_failed = CU_get_number_of_tests_failed();
+  CU_cleanup_registry();
+  if (CU_get_error() == CUE_SUCCESS) {
+    return num_tests_failed;
+  } else {
+    printf("CUnit Error: %s\n", CU_get_error_msg());
+    return CU_get_error();
+  }
+}
diff --git a/tests/failmalloc_test.c b/tests/failmalloc_test.c
new file mode 100644 (file)
index 0000000..984576c
--- /dev/null
@@ -0,0 +1,507 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012, 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "failmalloc_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "nghttp2_session.h"
+#include "nghttp2_stream.h"
+#include "nghttp2_frame.h"
+#include "nghttp2_helper.h"
+#include "malloc_wrapper.h"
+#include "nghttp2_test_helper.h"
+
+typedef struct {
+  uint8_t data[8192];
+  uint8_t *datamark, *datalimit;
+} data_feed;
+
+typedef struct {
+  data_feed *df;
+  size_t data_source_length;
+} my_user_data;
+
+static void data_feed_init(data_feed *df, nghttp2_bufs *bufs) {
+  nghttp2_buf *buf;
+  size_t data_length;
+
+  buf = &bufs->head->buf;
+  data_length = nghttp2_buf_len(buf);
+
+  assert(data_length <= sizeof(df->data));
+  memcpy(df->data, buf->pos, data_length);
+  df->datamark = df->data;
+  df->datalimit = df->data + data_length;
+}
+
+static ssize_t null_send_callback(nghttp2_session *session _U_,
+                                  const uint8_t *data _U_, size_t len,
+                                  int flags _U_, void *user_data _U_) {
+  return len;
+}
+
+static ssize_t data_feed_recv_callback(nghttp2_session *session _U_,
+                                       uint8_t *data, size_t len, int flags _U_,
+                                       void *user_data) {
+  data_feed *df = ((my_user_data *)user_data)->df;
+  size_t avail = df->datalimit - df->datamark;
+  size_t wlen = nghttp2_min(avail, len);
+  memcpy(data, df->datamark, wlen);
+  df->datamark += wlen;
+  return wlen;
+}
+
+static ssize_t fixed_length_data_source_read_callback(
+    nghttp2_session *session _U_, int32_t stream_id _U_, uint8_t *buf _U_,
+    size_t len, uint32_t *data_flags, nghttp2_data_source *source _U_,
+    void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  size_t wlen;
+  if (len < ud->data_source_length) {
+    wlen = len;
+  } else {
+    wlen = ud->data_source_length;
+  }
+  ud->data_source_length -= wlen;
+  if (ud->data_source_length == 0) {
+    *data_flags = NGHTTP2_DATA_FLAG_EOF;
+  }
+  return wlen;
+}
+
+#define TEST_FAILMALLOC_RUN(FUN)                                               \
+  do {                                                                         \
+    int nmalloc, i;                                                            \
+                                                                               \
+    nghttp2_failmalloc = 0;                                                    \
+    nghttp2_nmalloc = 0;                                                       \
+    FUN();                                                                     \
+    nmalloc = nghttp2_nmalloc;                                                 \
+                                                                               \
+    nghttp2_failmalloc = 1;                                                    \
+    for (i = 0; i < nmalloc; ++i) {                                            \
+      nghttp2_nmalloc = 0;                                                     \
+      nghttp2_failstart = i;                                                   \
+      /* printf("i=%zu\n", i); */                                              \
+      FUN();                                                                   \
+      /* printf("nmalloc=%d\n", nghttp2_nmalloc); */                           \
+    }                                                                          \
+    nghttp2_failmalloc = 0;                                                    \
+  } while (0)
+
+static void run_nghttp2_session_send(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"),
+                     MAKE_NV(":scheme", "https")};
+  nghttp2_data_provider data_prd;
+  nghttp2_settings_entry iv[2];
+  my_user_data ud;
+  int rv;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = 64 * 1024;
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 4096;
+  iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[1].value = 100;
+
+  rv = nghttp2_session_client_new3(&session, &callbacks, &ud, NULL,
+                                   nghttp2_mem_fm());
+  if (rv != 0) {
+    goto client_new_fail;
+  }
+  rv = nghttp2_submit_request(session, NULL, nv, ARRLEN(nv), &data_prd, NULL);
+  if (rv < 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, nv,
+                              ARRLEN(nv), NULL);
+  if (rv < 0) {
+    goto fail;
+  }
+  rv = nghttp2_session_send(session);
+  if (rv != 0) {
+    goto fail;
+  }
+  /* The HEADERS submitted by the previous nghttp2_submit_headers will
+     have stream ID 3. Send HEADERS to that stream. */
+  rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, NULL, nv,
+                              ARRLEN(nv), NULL);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 3, &data_prd);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_session_send(session);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 3, NGHTTP2_CANCEL);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_session_send(session);
+  if (rv != 0) {
+    goto fail;
+  }
+  /* Sending against half-closed stream */
+  rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 3, NULL, nv,
+                              ARRLEN(nv), NULL);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 3, &data_prd);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_session_send(session);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 100, NGHTTP2_NO_ERROR,
+                             NULL, 0);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = nghttp2_session_send(session);
+  if (rv != 0) {
+    goto fail;
+  }
+
+fail:
+  nghttp2_session_del(session);
+client_new_fail:
+  ;
+}
+
+void test_nghttp2_session_send(void) {
+  TEST_FAILMALLOC_RUN(run_nghttp2_session_send);
+}
+
+static void run_nghttp2_session_recv(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_hd_deflater deflater;
+  nghttp2_frame frame;
+  nghttp2_bufs bufs;
+  nghttp2_nv nv[] = {MAKE_NV(":authority", "example.org"),
+                     MAKE_NV(":scheme", "https")};
+  nghttp2_settings_entry iv[2];
+  my_user_data ud;
+  data_feed df;
+  int rv;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+
+  rv = frame_pack_bufs_init(&bufs);
+
+  if (rv != 0) {
+    return;
+  }
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.recv_callback = data_feed_recv_callback;
+  ud.df = &df;
+
+  nghttp2_failmalloc_pause();
+  nvlen = ARRLEN(nv);
+  nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm());
+  nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm());
+  nghttp2_session_server_new3(&session, &callbacks, &ud, NULL,
+                              nghttp2_mem_fm());
+  nghttp2_failmalloc_unpause();
+
+  /* HEADERS */
+  nghttp2_failmalloc_pause();
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1,
+                             NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen);
+  nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+  nghttp2_frame_headers_free(&frame.headers, nghttp2_mem_fm());
+  data_feed_init(&df, &bufs);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_failmalloc_unpause();
+
+  rv = nghttp2_session_recv(session);
+  if (rv != 0) {
+    goto fail;
+  }
+
+  /* PING */
+  nghttp2_failmalloc_pause();
+  nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL);
+  nghttp2_frame_pack_ping(&bufs, &frame.ping);
+  nghttp2_frame_ping_free(&frame.ping);
+  data_feed_init(&df, &bufs);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_failmalloc_unpause();
+
+  rv = nghttp2_session_recv(session);
+  if (rv != 0) {
+    goto fail;
+  }
+
+  /* RST_STREAM */
+  nghttp2_failmalloc_pause();
+  nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR);
+  nghttp2_frame_pack_rst_stream(&bufs, &frame.rst_stream);
+  nghttp2_frame_rst_stream_free(&frame.rst_stream);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_failmalloc_unpause();
+
+  rv = nghttp2_session_recv(session);
+  if (rv != 0) {
+    goto fail;
+  }
+
+  /* SETTINGS */
+  nghttp2_failmalloc_pause();
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 4096;
+  iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[1].value = 100;
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
+                              nghttp2_frame_iv_copy(iv, 2, nghttp2_mem_fm()),
+                              2);
+  nghttp2_frame_pack_settings(&bufs, &frame.settings);
+  nghttp2_frame_settings_free(&frame.settings, nghttp2_mem_fm());
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_failmalloc_unpause();
+
+  rv = nghttp2_session_recv(session);
+  if (rv != 0) {
+    goto fail;
+  }
+
+fail:
+  nghttp2_bufs_free(&bufs);
+  nghttp2_session_del(session);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_session_recv(void) {
+  TEST_FAILMALLOC_RUN(run_nghttp2_session_recv);
+}
+
+static void run_nghttp2_frame_pack_headers(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_frame frame, oframe;
+  nghttp2_bufs bufs;
+  nghttp2_nv nv[] = {MAKE_NV(":host", "example.org"),
+                     MAKE_NV(":scheme", "https")};
+  int rv;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+
+  rv = frame_pack_bufs_init(&bufs);
+
+  if (rv != 0) {
+    return;
+  }
+
+  rv = nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm());
+  if (rv != 0) {
+    goto deflate_init_fail;
+  }
+  rv = nghttp2_hd_inflate_init(&inflater, nghttp2_mem_fm());
+  if (rv != 0) {
+    goto inflate_init_fail;
+  }
+  nvlen = ARRLEN(nv);
+  rv = nghttp2_nv_array_copy(&nva, nv, nvlen, nghttp2_mem_fm());
+  if (rv < 0) {
+    goto nv_copy_fail;
+  }
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1,
+                             NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+  if (rv != 0) {
+    goto fail;
+  }
+  rv = unpack_framebuf(&oframe, &bufs);
+  if (rv != 0) {
+    goto fail;
+  }
+  nghttp2_frame_headers_free(&oframe.headers, nghttp2_mem_fm());
+
+fail:
+  nghttp2_frame_headers_free(&frame.headers, nghttp2_mem_fm());
+nv_copy_fail:
+  nghttp2_hd_inflate_free(&inflater);
+inflate_init_fail:
+  nghttp2_hd_deflate_free(&deflater);
+deflate_init_fail:
+  nghttp2_bufs_free(&bufs);
+}
+
+static void run_nghttp2_frame_pack_settings(void) {
+  nghttp2_frame frame, oframe;
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  nghttp2_settings_entry iv[2], *iv_copy;
+  int rv;
+
+  rv = frame_pack_bufs_init(&bufs);
+
+  if (rv != 0) {
+    return;
+  }
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 4096;
+  iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[1].value = 100;
+
+  iv_copy = nghttp2_frame_iv_copy(iv, 2, nghttp2_mem_fm());
+
+  if (iv_copy == NULL) {
+    goto iv_copy_fail;
+  }
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, iv_copy, 2);
+
+  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+  if (rv != 0) {
+    goto fail;
+  }
+
+  buf = &bufs.head->buf;
+
+  rv = nghttp2_frame_unpack_settings_payload2(
+      &oframe.settings.iv, &oframe.settings.niv, buf->pos + NGHTTP2_FRAME_HDLEN,
+      nghttp2_buf_len(buf) - NGHTTP2_FRAME_HDLEN, nghttp2_mem_fm());
+
+  if (rv != 0) {
+    goto fail;
+  }
+  nghttp2_frame_settings_free(&oframe.settings, nghttp2_mem_fm());
+
+fail:
+  nghttp2_frame_settings_free(&frame.settings, nghttp2_mem_fm());
+iv_copy_fail:
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_frame(void) {
+  TEST_FAILMALLOC_RUN(run_nghttp2_frame_pack_headers);
+  TEST_FAILMALLOC_RUN(run_nghttp2_frame_pack_settings);
+}
+
+static int deflate_inflate(nghttp2_hd_deflater *deflater,
+                           nghttp2_hd_inflater *inflater, nghttp2_bufs *bufs,
+                           nghttp2_nv *nva, size_t nvlen) {
+  int rv;
+
+  rv = nghttp2_hd_deflate_hd_bufs(deflater, bufs, nva, nvlen);
+
+  if (rv != 0) {
+    return rv;
+  }
+
+  rv = (int)inflate_hd(inflater, NULL, bufs, 0);
+
+  if (rv < 0) {
+    return rv;
+  }
+
+  nghttp2_bufs_reset(bufs);
+
+  return 0;
+}
+
+static void run_nghttp2_hd(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  int rv;
+  nghttp2_nv nva1[] = {MAKE_NV(":scheme", "https"),
+                       MAKE_NV(":authority", "example.org"),
+                       MAKE_NV(":path", "/slashdot"),
+                       MAKE_NV("accept-encoding", "gzip, deflate")};
+  nghttp2_nv nva2[] = {
+      MAKE_NV(":scheme", "https"), MAKE_NV(":authority", "example.org"),
+      MAKE_NV(":path", "/style.css"), MAKE_NV("cookie", "nghttp2=FTW")};
+
+  rv = frame_pack_bufs_init(&bufs);
+
+  if (rv != 0) {
+    return;
+  }
+
+  rv = nghttp2_hd_deflate_init(&deflater, nghttp2_mem_fm());
+
+  if (rv != 0) {
+    goto deflate_init_fail;
+  }
+
+  rv = nghttp2_hd_inflate_init(&inflater, nghttp2_mem_fm());
+
+  if (rv != 0) {
+    goto inflate_init_fail;
+  }
+
+  rv = deflate_inflate(&deflater, &inflater, &bufs, nva1, ARRLEN(nva1));
+
+  if (rv != 0) {
+    goto deflate_hd_fail;
+  }
+
+  rv = deflate_inflate(&deflater, &inflater, &bufs, nva2, ARRLEN(nva2));
+
+  if (rv != 0) {
+    goto deflate_hd_fail;
+  }
+
+deflate_hd_fail:
+  nghttp2_hd_inflate_free(&inflater);
+inflate_init_fail:
+  nghttp2_hd_deflate_free(&deflater);
+deflate_init_fail:
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_hd(void) { TEST_FAILMALLOC_RUN(run_nghttp2_hd); }
diff --git a/tests/failmalloc_test.h b/tests/failmalloc_test.h
new file mode 100644 (file)
index 0000000..1969bc1
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef FAILMALLOC_TEST_H
+#define FAILMALLOC_TEST_H
+
+void test_nghttp2_session_send(void);
+void test_nghttp2_session_recv(void);
+void test_nghttp2_frame(void);
+void test_nghttp2_hd(void);
+
+#endif /* FAILMALLOC_TEST_H */
diff --git a/tests/main.c b/tests/main.c
new file mode 100644 (file)
index 0000000..a3c9818
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <stdio.h>
+#include <string.h>
+#include <CUnit/Basic.h>
+/* include test cases' include files here */
+#include "nghttp2_pq_test.h"
+#include "nghttp2_map_test.h"
+#include "nghttp2_queue_test.h"
+#include "nghttp2_session_test.h"
+#include "nghttp2_frame_test.h"
+#include "nghttp2_stream_test.h"
+#include "nghttp2_hd_test.h"
+#include "nghttp2_npn_test.h"
+#include "nghttp2_helper_test.h"
+#include "nghttp2_buf_test.h"
+
+extern int nghttp2_enable_strict_first_settings_check;
+
+static int init_suite1(void) { return 0; }
+
+static int clean_suite1(void) { return 0; }
+
+int main(int argc _U_, char *argv[] _U_) {
+  CU_pSuite pSuite = NULL;
+  unsigned int num_tests_failed;
+
+  nghttp2_enable_strict_first_settings_check = 0;
+
+  /* initialize the CUnit test registry */
+  if (CUE_SUCCESS != CU_initialize_registry())
+    return CU_get_error();
+
+  /* add a suite to the registry */
+  pSuite = CU_add_suite("libnghttp2_TestSuite", init_suite1, clean_suite1);
+  if (NULL == pSuite) {
+    CU_cleanup_registry();
+    return CU_get_error();
+  }
+
+  /* add the tests to the suite */
+  if (!CU_add_test(pSuite, "pq", test_nghttp2_pq) ||
+      !CU_add_test(pSuite, "pq_update", test_nghttp2_pq_update) ||
+      !CU_add_test(pSuite, "map", test_nghttp2_map) ||
+      !CU_add_test(pSuite, "map_functional", test_nghttp2_map_functional) ||
+      !CU_add_test(pSuite, "map_each_free", test_nghttp2_map_each_free) ||
+      !CU_add_test(pSuite, "queue", test_nghttp2_queue) ||
+      !CU_add_test(pSuite, "npn", test_nghttp2_npn) ||
+      !CU_add_test(pSuite, "session_recv", test_nghttp2_session_recv) ||
+      !CU_add_test(pSuite, "session_recv_invalid_stream_id",
+                   test_nghttp2_session_recv_invalid_stream_id) ||
+      !CU_add_test(pSuite, "session_recv_invalid_frame",
+                   test_nghttp2_session_recv_invalid_frame) ||
+      !CU_add_test(pSuite, "session_recv_eof", test_nghttp2_session_recv_eof) ||
+      !CU_add_test(pSuite, "session_recv_data",
+                   test_nghttp2_session_recv_data) ||
+      !CU_add_test(pSuite, "session_recv_continuation",
+                   test_nghttp2_session_recv_continuation) ||
+      !CU_add_test(pSuite, "session_recv_headers_with_priority",
+                   test_nghttp2_session_recv_headers_with_priority) ||
+      !CU_add_test(pSuite, "session_recv_premature_headers",
+                   test_nghttp2_session_recv_premature_headers) ||
+      !CU_add_test(pSuite, "session_recv_unknown_frame",
+                   test_nghttp2_session_recv_unknown_frame) ||
+      !CU_add_test(pSuite, "session_recv_unexpected_continuation",
+                   test_nghttp2_session_recv_unexpected_continuation) ||
+      !CU_add_test(pSuite, "session_recv_settings_header_table_size",
+                   test_nghttp2_session_recv_settings_header_table_size) ||
+      !CU_add_test(pSuite, "session_recv_too_large_frame_length",
+                   test_nghttp2_session_recv_too_large_frame_length) ||
+      !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) ||
+      !CU_add_test(pSuite, "session_add_frame",
+                   test_nghttp2_session_add_frame) ||
+      !CU_add_test(pSuite, "session_on_request_headers_received",
+                   test_nghttp2_session_on_request_headers_received) ||
+      !CU_add_test(pSuite, "session_on_response_headers_received",
+                   test_nghttp2_session_on_response_headers_received) ||
+      !CU_add_test(pSuite, "session_on_headers_received",
+                   test_nghttp2_session_on_headers_received) ||
+      !CU_add_test(pSuite, "session_on_push_response_headers_received",
+                   test_nghttp2_session_on_push_response_headers_received) ||
+      !CU_add_test(pSuite, "session_on_priority_received",
+                   test_nghttp2_session_on_priority_received) ||
+      !CU_add_test(pSuite, "session_on_rst_stream_received",
+                   test_nghttp2_session_on_rst_stream_received) ||
+      !CU_add_test(pSuite, "session_on_settings_received",
+                   test_nghttp2_session_on_settings_received) ||
+      !CU_add_test(pSuite, "session_on_push_promise_received",
+                   test_nghttp2_session_on_push_promise_received) ||
+      !CU_add_test(pSuite, "session_on_ping_received",
+                   test_nghttp2_session_on_ping_received) ||
+      !CU_add_test(pSuite, "session_on_goaway_received",
+                   test_nghttp2_session_on_goaway_received) ||
+      !CU_add_test(pSuite, "session_on_window_update_received",
+                   test_nghttp2_session_on_window_update_received) ||
+      !CU_add_test(pSuite, "session_on_data_received",
+                   test_nghttp2_session_on_data_received) ||
+      !CU_add_test(pSuite, "session_send_headers_start_stream",
+                   test_nghttp2_session_send_headers_start_stream) ||
+      !CU_add_test(pSuite, "session_send_headers_reply",
+                   test_nghttp2_session_send_headers_reply) ||
+      !CU_add_test(pSuite, "session_send_headers_frame_size_error",
+                   test_nghttp2_session_send_headers_frame_size_error) ||
+      !CU_add_test(pSuite, "session_send_headers_push_reply",
+                   test_nghttp2_session_send_headers_push_reply) ||
+      !CU_add_test(pSuite, "session_send_rst_stream",
+                   test_nghttp2_session_send_rst_stream) ||
+      !CU_add_test(pSuite, "session_send_push_promise",
+                   test_nghttp2_session_send_push_promise) ||
+      !CU_add_test(pSuite, "session_is_my_stream_id",
+                   test_nghttp2_session_is_my_stream_id) ||
+      !CU_add_test(pSuite, "session_upgrade", test_nghttp2_session_upgrade) ||
+      !CU_add_test(pSuite, "session_reprioritize_stream",
+                   test_nghttp2_session_reprioritize_stream) ||
+      !CU_add_test(
+          pSuite, "session_reprioritize_stream_with_idle_stream_dep",
+          test_nghttp2_session_reprioritize_stream_with_idle_stream_dep) ||
+      !CU_add_test(pSuite, "submit_data", test_nghttp2_submit_data) ||
+      !CU_add_test(pSuite, "submit_data_read_length_too_large",
+                   test_nghttp2_submit_data_read_length_too_large) ||
+      !CU_add_test(pSuite, "submit_data_twice",
+                   test_nghttp2_submit_data_twice) ||
+      !CU_add_test(pSuite, "submit_request_with_data",
+                   test_nghttp2_submit_request_with_data) ||
+      !CU_add_test(pSuite, "submit_request_without_data",
+                   test_nghttp2_submit_request_without_data) ||
+      !CU_add_test(pSuite, "submit_response_with_data",
+                   test_nghttp2_submit_response_with_data) ||
+      !CU_add_test(pSuite, "submit_response_without_data",
+                   test_nghttp2_submit_response_without_data) ||
+      !CU_add_test(pSuite, "submit_headers_start_stream",
+                   test_nghttp2_submit_headers_start_stream) ||
+      !CU_add_test(pSuite, "submit_headers_reply",
+                   test_nghttp2_submit_headers_reply) ||
+      !CU_add_test(pSuite, "submit_headers_push_reply",
+                   test_nghttp2_submit_headers_push_reply) ||
+      !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) ||
+      !CU_add_test(pSuite, "submit_headers_continuation",
+                   test_nghttp2_submit_headers_continuation) ||
+      !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) ||
+      !CU_add_test(pSuite, "session_submit_settings",
+                   test_nghttp2_submit_settings) ||
+      !CU_add_test(pSuite, "session_submit_settings_update_local_window_size",
+                   test_nghttp2_submit_settings_update_local_window_size) ||
+      !CU_add_test(pSuite, "session_submit_push_promise",
+                   test_nghttp2_submit_push_promise) ||
+      !CU_add_test(pSuite, "submit_window_update",
+                   test_nghttp2_submit_window_update) ||
+      !CU_add_test(pSuite, "submit_window_update_local_window_size",
+                   test_nghttp2_submit_window_update_local_window_size) ||
+      !CU_add_test(pSuite, "submit_shutdown_notice",
+                   test_nghttp2_submit_shutdown_notice) ||
+      !CU_add_test(pSuite, "submit_invalid_nv",
+                   test_nghttp2_submit_invalid_nv) ||
+      !CU_add_test(pSuite, "session_open_stream",
+                   test_nghttp2_session_open_stream) ||
+      !CU_add_test(pSuite, "session_open_stream_with_idle_stream_dep",
+                   test_nghttp2_session_open_stream_with_idle_stream_dep) ||
+      !CU_add_test(pSuite, "session_get_next_ob_item",
+                   test_nghttp2_session_get_next_ob_item) ||
+      !CU_add_test(pSuite, "session_pop_next_ob_item",
+                   test_nghttp2_session_pop_next_ob_item) ||
+      !CU_add_test(pSuite, "session_reply_fail",
+                   test_nghttp2_session_reply_fail) ||
+      !CU_add_test(pSuite, "session_max_concurrent_streams",
+                   test_nghttp2_session_max_concurrent_streams) ||
+      !CU_add_test(pSuite, "session_stream_close_on_headers_push",
+                   test_nghttp2_session_stream_close_on_headers_push) ||
+      !CU_add_test(pSuite, "session_stop_data_with_rst_stream",
+                   test_nghttp2_session_stop_data_with_rst_stream) ||
+      !CU_add_test(pSuite, "session_defer_data",
+                   test_nghttp2_session_defer_data) ||
+      !CU_add_test(pSuite, "session_flow_control",
+                   test_nghttp2_session_flow_control) ||
+      !CU_add_test(pSuite, "session_flow_control_data_recv",
+                   test_nghttp2_session_flow_control_data_recv) ||
+      !CU_add_test(pSuite, "session_flow_control_data_with_padding_recv",
+                   test_nghttp2_session_flow_control_data_with_padding_recv) ||
+      !CU_add_test(pSuite, "session_data_read_temporal_failure",
+                   test_nghttp2_session_data_read_temporal_failure) ||
+      !CU_add_test(pSuite, "session_on_stream_close",
+                   test_nghttp2_session_on_stream_close) ||
+      !CU_add_test(pSuite, "session_on_ctrl_not_send",
+                   test_nghttp2_session_on_ctrl_not_send) ||
+      !CU_add_test(pSuite, "session_get_outbound_queue_size",
+                   test_nghttp2_session_get_outbound_queue_size) ||
+      !CU_add_test(pSuite, "session_get_effective_local_window_size",
+                   test_nghttp2_session_get_effective_local_window_size) ||
+      !CU_add_test(pSuite, "session_set_option",
+                   test_nghttp2_session_set_option) ||
+      !CU_add_test(pSuite, "session_data_backoff_by_high_pri_frame",
+                   test_nghttp2_session_data_backoff_by_high_pri_frame) ||
+      !CU_add_test(pSuite, "session_pack_data_with_padding",
+                   test_nghttp2_session_pack_data_with_padding) ||
+      !CU_add_test(pSuite, "session_pack_headers_with_padding",
+                   test_nghttp2_session_pack_headers_with_padding) ||
+      !CU_add_test(pSuite, "pack_settings_payload",
+                   test_nghttp2_pack_settings_payload) ||
+      !CU_add_test(pSuite, "session_stream_dep_add",
+                   test_nghttp2_session_stream_dep_add) ||
+      !CU_add_test(pSuite, "session_stream_dep_remove",
+                   test_nghttp2_session_stream_dep_remove) ||
+      !CU_add_test(pSuite, "session_stream_dep_add_subtree",
+                   test_nghttp2_session_stream_dep_add_subtree) ||
+      !CU_add_test(pSuite, "session_stream_dep_remove_subtree",
+                   test_nghttp2_session_stream_dep_remove_subtree) ||
+      !CU_add_test(
+          pSuite, "session_stream_dep_all_your_stream_are_belong_to_us",
+          test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us) ||
+      !CU_add_test(pSuite, "session_stream_attach_item",
+                   test_nghttp2_session_stream_attach_item) ||
+      !CU_add_test(pSuite, "session_stream_attach_item_subtree",
+                   test_nghttp2_session_stream_attach_item_subtree) ||
+      !CU_add_test(pSuite, "session_stream_keep_closed_stream",
+                   test_nghttp2_session_keep_closed_stream) ||
+      !CU_add_test(pSuite, "session_stream_keep_idle_stream",
+                   test_nghttp2_session_keep_idle_stream) ||
+      !CU_add_test(pSuite, "session_detach_idle_stream",
+                   test_nghttp2_session_detach_idle_stream) ||
+      !CU_add_test(pSuite, "session_large_dep_tree",
+                   test_nghttp2_session_large_dep_tree) ||
+      !CU_add_test(pSuite, "session_graceful_shutdown",
+                   test_nghttp2_session_graceful_shutdown) ||
+      !CU_add_test(pSuite, "session_on_header_temporal_failure",
+                   test_nghttp2_session_on_header_temporal_failure) ||
+      !CU_add_test(pSuite, "session_recv_client_preface",
+                   test_nghttp2_session_recv_client_preface) ||
+      !CU_add_test(pSuite, "session_delete_data_item",
+                   test_nghttp2_session_delete_data_item) ||
+      !CU_add_test(pSuite, "session_open_idle_stream",
+                   test_nghttp2_session_open_idle_stream) ||
+      !CU_add_test(pSuite, "frame_pack_headers",
+                   test_nghttp2_frame_pack_headers) ||
+      !CU_add_test(pSuite, "frame_pack_headers_frame_too_large",
+                   test_nghttp2_frame_pack_headers_frame_too_large) ||
+      !CU_add_test(pSuite, "frame_pack_headers_frame_smallest",
+                   test_nghttp2_submit_data_read_length_smallest) ||
+      !CU_add_test(pSuite, "frame_pack_priority",
+                   test_nghttp2_frame_pack_priority) ||
+      !CU_add_test(pSuite, "frame_pack_rst_stream",
+                   test_nghttp2_frame_pack_rst_stream) ||
+      !CU_add_test(pSuite, "frame_pack_settings",
+                   test_nghttp2_frame_pack_settings) ||
+      !CU_add_test(pSuite, "frame_pack_push_promise",
+                   test_nghttp2_frame_pack_push_promise) ||
+      !CU_add_test(pSuite, "frame_pack_ping", test_nghttp2_frame_pack_ping) ||
+      !CU_add_test(pSuite, "frame_pack_goaway",
+                   test_nghttp2_frame_pack_goaway) ||
+      !CU_add_test(pSuite, "frame_pack_window_update",
+                   test_nghttp2_frame_pack_window_update) ||
+      !CU_add_test(pSuite, "nv_array_copy", test_nghttp2_nv_array_copy) ||
+      !CU_add_test(pSuite, "iv_check", test_nghttp2_iv_check) ||
+      !CU_add_test(pSuite, "hd_deflate", test_nghttp2_hd_deflate) ||
+      !CU_add_test(pSuite, "hd_deflate_same_indexed_repr",
+                   test_nghttp2_hd_deflate_same_indexed_repr) ||
+      !CU_add_test(pSuite, "hd_inflate_indexed",
+                   test_nghttp2_hd_inflate_indexed) ||
+      !CU_add_test(pSuite, "hd_inflate_indname_noinc",
+                   test_nghttp2_hd_inflate_indname_noinc) ||
+      !CU_add_test(pSuite, "hd_inflate_indname_inc",
+                   test_nghttp2_hd_inflate_indname_inc) ||
+      !CU_add_test(pSuite, "hd_inflate_indname_inc_eviction",
+                   test_nghttp2_hd_inflate_indname_inc_eviction) ||
+      !CU_add_test(pSuite, "hd_inflate_newname_noinc",
+                   test_nghttp2_hd_inflate_newname_noinc) ||
+      !CU_add_test(pSuite, "hd_inflate_newname_inc",
+                   test_nghttp2_hd_inflate_newname_inc) ||
+      !CU_add_test(pSuite, "hd_inflate_clearall_inc",
+                   test_nghttp2_hd_inflate_clearall_inc) ||
+      !CU_add_test(pSuite, "hd_inflate_zero_length_huffman",
+                   test_nghttp2_hd_inflate_zero_length_huffman) ||
+      !CU_add_test(pSuite, "hd_ringbuf_reserve",
+                   test_nghttp2_hd_ringbuf_reserve) ||
+      !CU_add_test(pSuite, "hd_change_table_size",
+                   test_nghttp2_hd_change_table_size) ||
+      !CU_add_test(pSuite, "hd_deflate_inflate",
+                   test_nghttp2_hd_deflate_inflate) ||
+      !CU_add_test(pSuite, "hd_no_index", test_nghttp2_hd_no_index) ||
+      !CU_add_test(pSuite, "hd_deflate_bound", test_nghttp2_hd_deflate_bound) ||
+      !CU_add_test(pSuite, "hd_public_api", test_nghttp2_hd_public_api) ||
+      !CU_add_test(pSuite, "hd_decode_length", test_nghttp2_hd_decode_length) ||
+      !CU_add_test(pSuite, "hd_huff_encode", test_nghttp2_hd_huff_encode) ||
+      !CU_add_test(pSuite, "adjust_local_window_size",
+                   test_nghttp2_adjust_local_window_size) ||
+      !CU_add_test(pSuite, "check_header_name",
+                   test_nghttp2_check_header_name) ||
+      !CU_add_test(pSuite, "check_header_value",
+                   test_nghttp2_check_header_value) ||
+      !CU_add_test(pSuite, "bufs_add", test_nghttp2_bufs_add) ||
+      !CU_add_test(pSuite, "bufs_addb", test_nghttp2_bufs_addb) ||
+      !CU_add_test(pSuite, "bufs_orb", test_nghttp2_bufs_orb) ||
+      !CU_add_test(pSuite, "bufs_remove", test_nghttp2_bufs_remove) ||
+      !CU_add_test(pSuite, "bufs_reset", test_nghttp2_bufs_reset) ||
+      !CU_add_test(pSuite, "bufs_advance", test_nghttp2_bufs_advance) ||
+      !CU_add_test(pSuite, "bufs_next_present",
+                   test_nghttp2_bufs_next_present) ||
+      !CU_add_test(pSuite, "bufs_realloc", test_nghttp2_bufs_realloc)) {
+    CU_cleanup_registry();
+    return CU_get_error();
+  }
+
+  /* Run all tests using the CUnit Basic interface */
+  CU_basic_set_mode(CU_BRM_VERBOSE);
+  CU_basic_run_tests();
+  num_tests_failed = CU_get_number_of_tests_failed();
+  CU_cleanup_registry();
+  if (CU_get_error() == CUE_SUCCESS) {
+    return num_tests_failed;
+  } else {
+    printf("CUnit Error: %s\n", CU_get_error_msg());
+    return CU_get_error();
+  }
+}
diff --git a/tests/malloc_wrapper.c b/tests/malloc_wrapper.c
new file mode 100644 (file)
index 0000000..6873bff
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "malloc_wrapper.h"
+
+int nghttp2_failmalloc = 0;
+int nghttp2_failstart = 0;
+int nghttp2_countmalloc = 1;
+int nghttp2_nmalloc = 0;
+
+#define CHECK_PREREQ                                                           \
+  do {                                                                         \
+    if (nghttp2_failmalloc && nghttp2_nmalloc >= nghttp2_failstart) {          \
+      return NULL;                                                             \
+    }                                                                          \
+    if (nghttp2_countmalloc) {                                                 \
+      ++nghttp2_nmalloc;                                                       \
+    }                                                                          \
+  } while (0)
+
+static void *my_malloc(size_t size, void *mud _U_) {
+  CHECK_PREREQ;
+  return malloc(size);
+}
+
+static void my_free(void *ptr, void *mud _U_) { free(ptr); }
+
+static void *my_calloc(size_t nmemb, size_t size, void *mud _U_) {
+  CHECK_PREREQ;
+  return calloc(nmemb, size);
+}
+
+static void *my_realloc(void *ptr, size_t size, void *mud _U_) {
+  CHECK_PREREQ;
+  return realloc(ptr, size);
+}
+
+static nghttp2_mem mem = {NULL, my_malloc, my_free, my_calloc, my_realloc};
+
+nghttp2_mem *nghttp2_mem_fm(void) { return &mem; }
+
+static int failmalloc_bk, countmalloc_bk;
+
+void nghttp2_failmalloc_pause(void) {
+  failmalloc_bk = nghttp2_failmalloc;
+  countmalloc_bk = nghttp2_countmalloc;
+  nghttp2_failmalloc = 0;
+  nghttp2_countmalloc = 0;
+}
+
+void nghttp2_failmalloc_unpause(void) {
+  nghttp2_failmalloc = failmalloc_bk;
+  nghttp2_countmalloc = countmalloc_bk;
+}
diff --git a/tests/malloc_wrapper.h b/tests/malloc_wrapper.h
new file mode 100644 (file)
index 0000000..5405d09
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MALLOC_WRAPPER_H
+#define MALLOC_WRAPPER_H
+
+#include <stdlib.h>
+
+#include "nghttp2_mem.h"
+
+/* Global variables to control the behavior of malloc() */
+
+/* If nonzero, malloc failure mode is on */
+extern int nghttp2_failmalloc;
+/* If nghttp2_failstart <= nghttp2_nmalloc and nghttp2_failmalloc is
+   nonzero, malloc() fails. */
+extern int nghttp2_failstart;
+/* If nonzero, nghttp2_nmalloc is incremented if malloc() succeeds. */
+extern int nghttp2_countmalloc;
+/* The number of successful invocation of malloc(). This value is only
+   incremented if nghttp2_nmalloc is nonzero. */
+extern int nghttp2_nmalloc;
+
+/* Returns pointer to nghttp2_mem, which, when dereferenced, contains
+   specifically instrumented memory allocators for failmalloc
+   tests. */
+nghttp2_mem *nghttp2_mem_fm(void);
+
+/* Copies nghttp2_failmalloc and nghttp2_countmalloc to statically
+   allocated space and sets 0 to them. This will effectively make
+   malloc() work like normal malloc(). This is useful when you want to
+   disable malloc() failure mode temporarily. */
+void nghttp2_failmalloc_pause(void);
+
+/* Restores the values of nghttp2_failmalloc and nghttp2_countmalloc
+   with the values saved by the previous
+   nghttp2_failmalloc_pause(). */
+void nghttp2_failmalloc_unpause(void);
+
+#endif /* MALLOC_WRAPPER_H */
diff --git a/tests/nghttp2_buf_test.c b/tests/nghttp2_buf_test.c
new file mode 100644 (file)
index 0000000..e6765f2
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_buf_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_buf.h"
+#include "nghttp2_test_helper.h"
+
+void test_nghttp2_bufs_add(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  uint8_t data[2048];
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init(&bufs, 1000, 3, mem);
+  CU_ASSERT(0 == rv);
+
+  CU_ASSERT(bufs.cur->buf.pos == bufs.cur->buf.last);
+
+  rv = nghttp2_bufs_add(&bufs, data, 493);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(493 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(493 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(507 == nghttp2_bufs_cur_avail(&bufs));
+
+  rv = nghttp2_bufs_add(&bufs, data, 507);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1000 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1000 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(bufs.cur == bufs.head);
+
+  rv = nghttp2_bufs_add(&bufs, data, 1);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1001 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(bufs.cur == bufs.head->next);
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_addb(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  ssize_t i;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init(&bufs, 1000, 3, mem);
+  CU_ASSERT(0 == rv);
+
+  rv = nghttp2_bufs_addb(&bufs, 14);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(14 == *bufs.cur->buf.pos);
+
+  for (i = 0; i < 999; ++i) {
+    rv = nghttp2_bufs_addb(&bufs, 254);
+
+    CU_ASSERT(0 == rv);
+    CU_ASSERT(i + 2 == nghttp2_buf_len(&bufs.cur->buf));
+    CU_ASSERT(i + 2 == nghttp2_bufs_len(&bufs));
+    CU_ASSERT(254 == *(bufs.cur->buf.last - 1));
+    CU_ASSERT(bufs.cur == bufs.head);
+  }
+
+  rv = nghttp2_bufs_addb(&bufs, 253);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1001 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(253 == *(bufs.cur->buf.last - 1));
+  CU_ASSERT(bufs.cur == bufs.head->next);
+
+  rv = nghttp2_bufs_addb_hold(&bufs, 15);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1001 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(15 == *(bufs.cur->buf.last));
+
+  /* test fast version */
+
+  nghttp2_bufs_fast_addb(&bufs, 240);
+
+  CU_ASSERT(2 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1002 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(240 == *(bufs.cur->buf.last - 1));
+
+  nghttp2_bufs_fast_addb_hold(&bufs, 113);
+
+  CU_ASSERT(2 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1002 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(113 == *(bufs.cur->buf.last));
+
+  /* addb_hold when last == end */
+  bufs.cur->buf.last = bufs.cur->buf.end;
+
+  rv = nghttp2_bufs_addb_hold(&bufs, 19);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(0 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(2000 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(19 == *(bufs.cur->buf.last));
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_orb(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init(&bufs, 1000, 3, mem);
+  CU_ASSERT(0 == rv);
+
+  *(bufs.cur->buf.last) = 0;
+
+  rv = nghttp2_bufs_orb_hold(&bufs, 15);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(0 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(0 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(15 == *(bufs.cur->buf.last));
+
+  rv = nghttp2_bufs_orb(&bufs, 240);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == nghttp2_buf_len(&bufs.cur->buf));
+  CU_ASSERT(1 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(255 == *(bufs.cur->buf.last - 1));
+
+  *(bufs.cur->buf.last) = 0;
+  nghttp2_bufs_fast_orb_hold(&bufs, 240);
+  CU_ASSERT(240 == *(bufs.cur->buf.last));
+
+  nghttp2_bufs_fast_orb(&bufs, 15);
+  CU_ASSERT(255 == *(bufs.cur->buf.last - 1));
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_remove(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  nghttp2_buf_chain *chain;
+  int i;
+  uint8_t *out;
+  ssize_t outlen;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init(&bufs, 1000, 3, mem);
+  CU_ASSERT(0 == rv);
+
+  nghttp2_buf_shift_right(&bufs.cur->buf, 10);
+
+  rv = nghttp2_bufs_add(&bufs, "hello ", 6);
+  CU_ASSERT(0 == rv);
+
+  for (i = 0; i < 2; ++i) {
+    chain = bufs.cur;
+
+    rv = nghttp2_bufs_advance(&bufs);
+    CU_ASSERT(0 == rv);
+
+    CU_ASSERT(chain->next == bufs.cur);
+  }
+
+  rv = nghttp2_bufs_add(&bufs, "world", 5);
+  CU_ASSERT(0 == rv);
+
+  outlen = nghttp2_bufs_remove(&bufs, &out);
+  CU_ASSERT(11 == outlen);
+
+  CU_ASSERT(0 == memcmp("hello world", out, outlen));
+  CU_ASSERT(0 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(bufs.cur->buf.pos == bufs.cur->buf.begin);
+
+  free(out);
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_reset(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  nghttp2_buf_chain *ci;
+  ssize_t offset = 9;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init3(&bufs, 250, 3, 1, offset, mem);
+  CU_ASSERT(0 == rv);
+
+  rv = nghttp2_bufs_add(&bufs, "foo", 3);
+  CU_ASSERT(0 == rv);
+
+  rv = nghttp2_bufs_advance(&bufs);
+  CU_ASSERT(0 == rv);
+
+  rv = nghttp2_bufs_add(&bufs, "bar", 3);
+  CU_ASSERT(0 == rv);
+
+  CU_ASSERT(6 == nghttp2_bufs_len(&bufs));
+
+  nghttp2_bufs_reset(&bufs);
+
+  CU_ASSERT(0 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(bufs.cur == bufs.head);
+
+  for (ci = bufs.head; ci; ci = ci->next) {
+    CU_ASSERT(offset == ci->buf.pos - ci->buf.begin);
+    CU_ASSERT(ci->buf.pos == ci->buf.last);
+  }
+
+  CU_ASSERT(bufs.head->next == NULL);
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_advance(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  int i;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init(&bufs, 250, 3, mem);
+  CU_ASSERT(0 == rv);
+
+  for (i = 0; i < 2; ++i) {
+    rv = nghttp2_bufs_advance(&bufs);
+    CU_ASSERT(0 == rv);
+  }
+
+  rv = nghttp2_bufs_advance(&bufs);
+  CU_ASSERT(NGHTTP2_ERR_BUFFER_ERROR == rv);
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_next_present(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init(&bufs, 250, 3, mem);
+  CU_ASSERT(0 == rv);
+
+  CU_ASSERT(0 == nghttp2_bufs_next_present(&bufs));
+
+  rv = nghttp2_bufs_advance(&bufs);
+  CU_ASSERT(0 == rv);
+
+  nghttp2_bufs_rewind(&bufs);
+
+  CU_ASSERT(0 == nghttp2_bufs_next_present(&bufs));
+
+  bufs.cur = bufs.head->next;
+
+  rv = nghttp2_bufs_addb(&bufs, 1);
+  CU_ASSERT(0 == rv);
+
+  nghttp2_bufs_rewind(&bufs);
+
+  CU_ASSERT(0 != nghttp2_bufs_next_present(&bufs));
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_bufs_realloc(void) {
+  int rv;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  rv = nghttp2_bufs_init3(&bufs, 266, 3, 1, 10, mem);
+  CU_ASSERT(0 == rv);
+
+  /* Create new buffer to see that these buffers are deallocated on
+     realloc */
+  rv = nghttp2_bufs_advance(&bufs);
+  CU_ASSERT(0 == rv);
+
+  rv = nghttp2_bufs_realloc(&bufs, 522);
+  CU_ASSERT(0 == rv);
+
+  CU_ASSERT(512 == nghttp2_bufs_cur_avail(&bufs));
+
+  rv = nghttp2_bufs_realloc(&bufs, 9);
+  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == rv);
+
+  nghttp2_bufs_free(&bufs);
+}
diff --git a/tests/nghttp2_buf_test.h b/tests/nghttp2_buf_test.h
new file mode 100644 (file)
index 0000000..5da388e
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2014 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_BUF_TEST_H
+#define NGHTTP2_BUF_TEST_H
+
+void test_nghttp2_bufs_add(void);
+void test_nghttp2_bufs_addb(void);
+void test_nghttp2_bufs_orb(void);
+void test_nghttp2_bufs_remove(void);
+void test_nghttp2_bufs_reset(void);
+void test_nghttp2_bufs_advance(void);
+void test_nghttp2_bufs_next_present(void);
+void test_nghttp2_bufs_realloc(void);
+
+#endif /* NGHTTP2_BUF_TEST_H */
diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c
new file mode 100644 (file)
index 0000000..2f501f7
--- /dev/null
@@ -0,0 +1,559 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_frame_test.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_frame.h"
+#include "nghttp2_helper.h"
+#include "nghttp2_test_helper.h"
+#include "nghttp2_priority_spec.h"
+
+static nghttp2_nv make_nv(const char *name, const char *value) {
+  nghttp2_nv nv;
+  nv.name = (uint8_t *)name;
+  nv.value = (uint8_t *)value;
+  nv.namelen = strlen(name);
+  nv.valuelen = strlen(value);
+  nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+  return nv;
+}
+
+#define HEADERS_LENGTH 7
+
+static nghttp2_nv *headers(void) {
+  nghttp2_nv *nva = malloc(sizeof(nghttp2_nv) * HEADERS_LENGTH);
+  nva[0] = make_nv("method", "GET");
+  nva[1] = make_nv("scheme", "https");
+  nva[2] = make_nv("url", "/");
+  nva[3] = make_nv("x-head", "foo");
+  nva[4] = make_nv("x-head", "bar");
+  nva[5] = make_nv("version", "HTTP/1.1");
+  nva[6] = make_nv("x-empty", "");
+  return nva;
+}
+
+static void check_frame_header(size_t length, uint8_t type, uint8_t flags,
+                               int32_t stream_id, nghttp2_frame_hd *hd) {
+  CU_ASSERT(length == hd->length);
+  CU_ASSERT(type == hd->type);
+  CU_ASSERT(flags == hd->flags);
+  CU_ASSERT(stream_id == hd->stream_id);
+  CU_ASSERT(0 == hd->reserved);
+}
+
+void test_nghttp2_frame_pack_headers() {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_headers frame, oframe;
+  nghttp2_bufs bufs;
+  nghttp2_nv *nva;
+  nghttp2_priority_spec pri_spec;
+  ssize_t nvlen;
+  nva_out out;
+  ssize_t hdblocklen;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  nva = headers();
+  nvlen = HEADERS_LENGTH;
+
+  nghttp2_priority_spec_default_init(&pri_spec);
+
+  nghttp2_frame_headers_init(
+      &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007,
+      NGHTTP2_HCAT_REQUEST, &pri_spec, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater);
+
+  nghttp2_bufs_rewind(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+
+  check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN,
+                     NGHTTP2_HEADERS,
+                     NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS,
+                     1000000007, &oframe.hd);
+  /* We did not include PRIORITY flag */
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == oframe.pri_spec.weight);
+
+  hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN;
+  CU_ASSERT(hdblocklen ==
+            inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN));
+
+  CU_ASSERT(7 == out.nvlen);
+  CU_ASSERT(nvnameeq("method", &out.nva[0]));
+  CU_ASSERT(nvvalueeq("GET", &out.nva[0]));
+
+  nghttp2_frame_headers_free(&oframe, mem);
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  memset(&oframe, 0, sizeof(oframe));
+  /* Next, include NGHTTP2_FLAG_PRIORITY */
+  nghttp2_priority_spec_init(&frame.pri_spec, 1000000009, 12, 1);
+  frame.hd.flags |= NGHTTP2_FLAG_PRIORITY;
+
+  rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+
+  check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN,
+                     NGHTTP2_HEADERS,
+                     NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS |
+                         NGHTTP2_FLAG_PRIORITY,
+                     1000000007, &oframe.hd);
+
+  CU_ASSERT(1000000009 == oframe.pri_spec.stream_id);
+  CU_ASSERT(12 == oframe.pri_spec.weight);
+  CU_ASSERT(1 == oframe.pri_spec.exclusive);
+
+  hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN -
+               nghttp2_frame_priority_len(oframe.hd.flags);
+  CU_ASSERT(hdblocklen ==
+            inflate_hd(&inflater, &out, &bufs,
+                       NGHTTP2_FRAME_HDLEN +
+                           nghttp2_frame_priority_len(oframe.hd.flags)));
+
+  nghttp2_nv_array_sort(out.nva, out.nvlen);
+  CU_ASSERT(nvnameeq("method", &out.nva[0]));
+
+  nghttp2_frame_headers_free(&oframe, mem);
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_headers_free(&frame, mem);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_frame_pack_headers_frame_too_large(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_headers frame;
+  nghttp2_bufs bufs;
+  nghttp2_nv *nva;
+  size_t big_vallen = NGHTTP2_HD_MAX_NV;
+  nghttp2_nv big_hds[16];
+  size_t big_hdslen = ARRLEN(big_hds);
+  size_t i;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  for (i = 0; i < big_hdslen; ++i) {
+    big_hds[i].name = (uint8_t *)"header";
+    big_hds[i].value = malloc(big_vallen + 1);
+    memset(big_hds[i].value, '0' + (int)i, big_vallen);
+    big_hds[i].value[big_vallen] = '\0';
+    big_hds[i].namelen = strlen((char *)big_hds[i].name);
+    big_hds[i].valuelen = big_vallen;
+    big_hds[i].flags = NGHTTP2_NV_FLAG_NONE;
+  }
+
+  nghttp2_nv_array_copy(&nva, big_hds, big_hdslen, mem);
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nghttp2_frame_headers_init(
+      &frame, NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS, 1000000007,
+      NGHTTP2_HCAT_REQUEST, NULL, nva, big_hdslen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame, &deflater);
+  CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == rv);
+
+  nghttp2_frame_headers_free(&frame, mem);
+  nghttp2_bufs_free(&bufs);
+  for (i = 0; i < big_hdslen; ++i) {
+    free(big_hds[i].value);
+  }
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_frame_pack_priority(void) {
+  nghttp2_priority frame, oframe;
+  nghttp2_bufs bufs;
+  nghttp2_priority_spec pri_spec;
+  int rv;
+
+  frame_pack_bufs_init(&bufs);
+
+  /* First, pack priority with priority group and weight */
+  nghttp2_priority_spec_init(&pri_spec, 1000000009, 12, 1);
+
+  nghttp2_frame_priority_init(&frame, 1000000007, &pri_spec);
+  rv = nghttp2_frame_pack_priority(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(5, NGHTTP2_PRIORITY, NGHTTP2_FLAG_NONE, 1000000007,
+                     &oframe.hd);
+
+  CU_ASSERT(1000000009 == oframe.pri_spec.stream_id);
+  CU_ASSERT(12 == oframe.pri_spec.weight);
+  CU_ASSERT(1 == oframe.pri_spec.exclusive);
+
+  nghttp2_frame_priority_free(&oframe);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_priority_free(&frame);
+}
+
+void test_nghttp2_frame_pack_rst_stream(void) {
+  nghttp2_rst_stream frame, oframe;
+  nghttp2_bufs bufs;
+  int rv;
+
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_frame_rst_stream_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR);
+  rv = nghttp2_frame_pack_rst_stream(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007,
+                     &oframe.hd);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == oframe.error_code);
+
+  nghttp2_frame_rst_stream_free(&oframe);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Unknown error code is passed to callback as is */
+  frame.error_code = 1000000009;
+  rv = nghttp2_frame_pack_rst_stream(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+
+  check_frame_header(4, NGHTTP2_RST_STREAM, NGHTTP2_FLAG_NONE, 1000000007,
+                     &oframe.hd);
+
+  CU_ASSERT(1000000009 == oframe.error_code);
+
+  nghttp2_frame_rst_stream_free(&oframe);
+
+  nghttp2_frame_rst_stream_free(&frame);
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_frame_pack_settings() {
+  nghttp2_settings frame, oframe;
+  nghttp2_bufs bufs;
+  int i;
+  int rv;
+  nghttp2_settings_entry iv[] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 256},
+                                 {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 16384},
+                                 {NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, 4096}};
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_frame_settings_init(&frame, NGHTTP2_FLAG_NONE,
+                              nghttp2_frame_iv_copy(iv, 3, mem), 3);
+  rv = nghttp2_frame_pack_settings(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 3 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH ==
+            nghttp2_bufs_len(&bufs));
+
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(3 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH, NGHTTP2_SETTINGS,
+                     NGHTTP2_FLAG_NONE, 0, &oframe.hd);
+  CU_ASSERT(3 == oframe.niv);
+  for (i = 0; i < 3; ++i) {
+    CU_ASSERT(iv[i].settings_id == oframe.iv[i].settings_id);
+    CU_ASSERT(iv[i].value == oframe.iv[i].value);
+  }
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_settings_free(&frame, mem);
+  nghttp2_frame_settings_free(&oframe, mem);
+}
+
+void test_nghttp2_frame_pack_push_promise() {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_push_promise frame, oframe;
+  nghttp2_bufs bufs;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+  nva_out out;
+  ssize_t hdblocklen;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  nva = headers();
+  nvlen = HEADERS_LENGTH;
+  nghttp2_frame_push_promise_init(&frame, NGHTTP2_FLAG_END_HEADERS, 1000000007,
+                                  (1U << 31) - 1, nva, nvlen);
+  rv = nghttp2_frame_pack_push_promise(&bufs, &frame, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+
+  check_frame_header(nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN,
+                     NGHTTP2_PUSH_PROMISE, NGHTTP2_FLAG_END_HEADERS, 1000000007,
+                     &oframe.hd);
+  CU_ASSERT((1U << 31) - 1 == oframe.promised_stream_id);
+
+  hdblocklen = nghttp2_bufs_len(&bufs) - NGHTTP2_FRAME_HDLEN - 4;
+  CU_ASSERT(hdblocklen ==
+            inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN + 4));
+
+  CU_ASSERT(7 == out.nvlen);
+  CU_ASSERT(nvnameeq("method", &out.nva[0]));
+  CU_ASSERT(nvvalueeq("GET", &out.nva[0]));
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_push_promise_free(&oframe, mem);
+  nghttp2_frame_push_promise_free(&frame, mem);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_frame_pack_ping(void) {
+  nghttp2_ping frame, oframe;
+  nghttp2_bufs bufs;
+  const uint8_t opaque_data[] = "01234567";
+  int rv;
+
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_frame_ping_init(&frame, NGHTTP2_FLAG_ACK, opaque_data);
+  rv = nghttp2_frame_pack_ping(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 8 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(8, NGHTTP2_PING, NGHTTP2_FLAG_ACK, 0, &oframe.hd);
+  CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, sizeof(opaque_data) - 1) ==
+            0);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_ping_free(&oframe);
+  nghttp2_frame_ping_free(&frame);
+}
+
+void test_nghttp2_frame_pack_goaway() {
+  nghttp2_goaway frame, oframe;
+  nghttp2_bufs bufs;
+  size_t opaque_data_len = 16;
+  uint8_t *opaque_data = malloc(opaque_data_len);
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memcpy(opaque_data, "0123456789abcdef", opaque_data_len);
+  nghttp2_frame_goaway_init(&frame, 1000000007, NGHTTP2_PROTOCOL_ERROR,
+                            opaque_data, opaque_data_len);
+  rv = nghttp2_frame_pack_goaway(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT((ssize_t)(NGHTTP2_FRAME_HDLEN + 8 + opaque_data_len) ==
+            nghttp2_bufs_len(&bufs));
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd);
+  CU_ASSERT(1000000007 == oframe.last_stream_id);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == oframe.error_code);
+
+  CU_ASSERT(opaque_data_len == oframe.opaque_data_len);
+  CU_ASSERT(memcmp(opaque_data, oframe.opaque_data, opaque_data_len) == 0);
+
+  nghttp2_frame_goaway_free(&oframe, mem);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Unknown error code is passed to callback as is */
+  frame.error_code = 1000000009;
+
+  rv = nghttp2_frame_pack_goaway(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(24, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, &oframe.hd);
+  CU_ASSERT(1000000009 == oframe.error_code);
+
+  nghttp2_frame_goaway_free(&oframe, mem);
+
+  nghttp2_frame_goaway_free(&frame, mem);
+
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_frame_pack_window_update(void) {
+  nghttp2_window_update frame, oframe;
+  nghttp2_bufs bufs;
+  int rv;
+
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_frame_window_update_init(&frame, NGHTTP2_FLAG_NONE, 1000000007, 4096);
+  rv = nghttp2_frame_pack_window_update(&bufs, &frame);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4 == nghttp2_bufs_len(&bufs));
+  CU_ASSERT(0 == unpack_framebuf((nghttp2_frame *)&oframe, &bufs));
+  check_frame_header(4, NGHTTP2_WINDOW_UPDATE, NGHTTP2_FLAG_NONE, 1000000007,
+                     &oframe.hd);
+  CU_ASSERT(4096 == oframe.window_size_increment);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_window_update_free(&oframe);
+  nghttp2_frame_window_update_free(&frame);
+}
+
+void test_nghttp2_nv_array_copy(void) {
+  nghttp2_nv *nva;
+  ssize_t rv;
+  nghttp2_nv emptynv[] = {MAKE_NV("", ""), MAKE_NV("", "")};
+  nghttp2_nv nv[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")};
+  nghttp2_nv bignv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  bignv.name = (uint8_t *)"echo";
+  bignv.namelen = strlen("echo");
+  bignv.valuelen = (1 << 14) - 1;
+  bignv.value = malloc(bignv.valuelen);
+  memset(bignv.value, '0', bignv.valuelen);
+
+  rv = nghttp2_nv_array_copy(&nva, NULL, 0, mem);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NULL == nva);
+
+  rv = nghttp2_nv_array_copy(&nva, emptynv, ARRLEN(emptynv), mem);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nva[0].namelen == 0);
+  CU_ASSERT(nva[0].valuelen == 0);
+  CU_ASSERT(nva[1].namelen == 0);
+  CU_ASSERT(nva[1].valuelen == 0);
+
+  nghttp2_nv_array_del(nva, mem);
+
+  rv = nghttp2_nv_array_copy(&nva, nv, ARRLEN(nv), mem);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nva[0].namelen == 5);
+  CU_ASSERT(0 == memcmp("alpha", nva[0].name, 5));
+  CU_ASSERT(nva[0].valuelen = 5);
+  CU_ASSERT(0 == memcmp("bravo", nva[0].value, 5));
+  CU_ASSERT(nva[1].namelen == 7);
+  CU_ASSERT(0 == memcmp("charlie", nva[1].name, 7));
+  CU_ASSERT(nva[1].valuelen == 5);
+  CU_ASSERT(0 == memcmp("delta", nva[1].value, 5));
+
+  nghttp2_nv_array_del(nva, mem);
+
+  /* Large header field is acceptable */
+  rv = nghttp2_nv_array_copy(&nva, &bignv, 1, mem);
+  CU_ASSERT(0 == rv);
+
+  nghttp2_nv_array_del(nva, mem);
+
+  free(bignv.value);
+}
+
+void test_nghttp2_iv_check(void) {
+  nghttp2_settings_entry iv[5];
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = 100;
+  iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[1].value = 1024;
+
+  CU_ASSERT(nghttp2_iv_check(iv, 2));
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = NGHTTP2_MAX_WINDOW_SIZE;
+  CU_ASSERT(nghttp2_iv_check(iv, 2));
+
+  /* Too large window size */
+  iv[1].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1;
+  CU_ASSERT(0 == nghttp2_iv_check(iv, 2));
+
+  /* ENABLE_PUSH only allows 0 or 1 */
+  iv[1].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  iv[1].value = 0;
+  CU_ASSERT(nghttp2_iv_check(iv, 2));
+  iv[1].value = 1;
+  CU_ASSERT(nghttp2_iv_check(iv, 2));
+  iv[1].value = 3;
+  CU_ASSERT(!nghttp2_iv_check(iv, 2));
+
+  /* Undefined SETTINGS ID is allowed */
+  iv[1].settings_id = 1000000009;
+  iv[1].value = 0;
+  CU_ASSERT(nghttp2_iv_check(iv, 2));
+
+  /* Too large SETTINGS_HEADER_TABLE_SIZE */
+  iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[1].value = UINT32_MAX;
+  CU_ASSERT(!nghttp2_iv_check(iv, 2));
+
+  /* Too small SETTINGS_MAX_FRAME_SIZE */
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+  iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN - 1;
+  CU_ASSERT(!nghttp2_iv_check(iv, 1));
+
+  /* Too large SETTINGS_MAX_FRAME_SIZE */
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+  iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
+  CU_ASSERT(!nghttp2_iv_check(iv, 1));
+
+  /* Max and min SETTINGS_MAX_FRAME_SIZE */
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+  iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN;
+  iv[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+  iv[1].value = NGHTTP2_MAX_FRAME_SIZE_MAX;
+  CU_ASSERT(nghttp2_iv_check(iv, 2));
+}
diff --git a/tests/nghttp2_frame_test.h b/tests/nghttp2_frame_test.h
new file mode 100644 (file)
index 0000000..a0ce37b
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_FRAME_TEST_H
+#define NGHTTP2_FRAME_TEST_H
+
+void test_nghttp2_frame_pack_headers(void);
+void test_nghttp2_frame_pack_headers_frame_too_large(void);
+void test_nghttp2_frame_pack_priority(void);
+void test_nghttp2_frame_pack_rst_stream(void);
+void test_nghttp2_frame_pack_settings(void);
+void test_nghttp2_frame_pack_push_promise(void);
+void test_nghttp2_frame_pack_ping(void);
+void test_nghttp2_frame_pack_goaway(void);
+void test_nghttp2_frame_pack_window_update(void);
+void test_nghttp2_nv_array_copy(void);
+void test_nghttp2_iv_check(void);
+
+#endif /* NGHTTP2_FRAME_TEST_H */
diff --git a/tests/nghttp2_hd_test.c b/tests/nghttp2_hd_test.c
new file mode 100644 (file)
index 0000000..5630002
--- /dev/null
@@ -0,0 +1,1242 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_hd_test.h"
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_hd.h"
+#include "nghttp2_frame.h"
+#include "nghttp2_test_helper.h"
+
+#define GET_TABLE_ENT(context, index) nghttp2_hd_table_get(context, index)
+
+void test_nghttp2_hd_deflate(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_nv nva1[] = {MAKE_NV(":path", "/my-example/index.html"),
+                       MAKE_NV(":scheme", "https"), MAKE_NV("hello", "world")};
+  nghttp2_nv nva2[] = {MAKE_NV(":path", "/script.js"),
+                       MAKE_NV(":scheme", "https")};
+  nghttp2_nv nva3[] = {MAKE_NV("cookie", "k1=v1"), MAKE_NV("cookie", "k2=v2"),
+                       MAKE_NV("via", "proxy")};
+  nghttp2_nv nva4[] = {MAKE_NV(":path", "/style.css"),
+                       MAKE_NV("cookie", "k1=v1"), MAKE_NV("cookie", "k1=v1")};
+  nghttp2_nv nva5[] = {MAKE_NV(":path", "/style.css"),
+                       MAKE_NV("x-nghttp2", "")};
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nva_out out;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, mem));
+  CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, mem));
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(3 == out.nvlen);
+  assert_nv_equal(nva1, out.nva, 3);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Second headers */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(2 == out.nvlen);
+  assert_nv_equal(nva2, out.nva, 2);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Third headers, including same header field name, but value is not
+     the same. */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva3, ARRLEN(nva3));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(3 == out.nvlen);
+  assert_nv_equal(nva3, out.nva, 3);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Fourth headers, including duplicate header fields. */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva4, ARRLEN(nva4));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(3 == out.nvlen);
+  assert_nv_equal(nva4, out.nva, 3);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Fifth headers includes empty value */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva5, ARRLEN(nva5));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(2 == out.nvlen);
+  assert_nv_equal(nva5, out.nva, 2);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Cleanup */
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_hd_deflate_same_indexed_repr(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_nv nva1[] = {MAKE_NV("cookie", "alpha"), MAKE_NV("cookie", "alpha")};
+  nghttp2_nv nva2[] = {MAKE_NV("cookie", "alpha"), MAKE_NV("cookie", "alpha"),
+                       MAKE_NV("cookie", "alpha")};
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nva_out out;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  CU_ASSERT(0 == nghttp2_hd_deflate_init(&deflater, mem));
+  CU_ASSERT(0 == nghttp2_hd_inflate_init(&inflater, mem));
+
+  /* Encode 2 same headers.  Emit 1 literal reprs and 1 index repr. */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva1, ARRLEN(nva1));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(2 == out.nvlen);
+  assert_nv_equal(nva1, out.nva, 2);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Encode 3 same headers.  This time, emits 3 index reprs. */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, ARRLEN(nva2));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen == 3);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(3 == out.nvlen);
+  assert_nv_equal(nva2, out.nva, 3);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Cleanup */
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_hd_inflate_indexed(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nv = MAKE_NV(":path", "/");
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  nghttp2_bufs_addb(&bufs, (1 << 7) | 4);
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(1 == blocklen);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+
+  assert_nv_equal(&nv, out.nva, 1);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* index = 0 is error */
+  nghttp2_bufs_addb(&bufs, 1 << 7);
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(1 == blocklen);
+  CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == inflate_hd(&inflater, &out, &bufs, 0));
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_indname_noinc(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nv[] = {/* Huffman */
+                     MAKE_NV("user-agent", "nghttp2"),
+                     /* Expecting no huffman */
+                     MAKE_NV("user-agent", "x")};
+  size_t i;
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  for (i = 0; i < ARRLEN(nv); ++i) {
+    CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv[i], 0));
+
+    blocklen = nghttp2_bufs_len(&bufs);
+
+    CU_ASSERT(blocklen > 0);
+    CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+    CU_ASSERT(1 == out.nvlen);
+    assert_nv_equal(&nv[i], out.nva, 1);
+    CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+    nva_out_reset(&out);
+    nghttp2_bufs_reset(&bufs);
+  }
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_indname_inc(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nv = MAKE_NV("user-agent", "nghttp2");
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 57, &nv, 1));
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+  assert_nv_equal(&nv, out.nva, 1);
+  CU_ASSERT(1 == inflater.ctx.hd_table.len);
+  assert_nv_equal(
+      &nv, &GET_TABLE_ENT(&inflater.ctx, NGHTTP2_STATIC_TABLE_LENGTH +
+                                             inflater.ctx.hd_table.len - 1)->nv,
+      1);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_indname_inc_eviction(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  uint8_t value[1024];
+  nva_out out;
+  nghttp2_nv nv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  memset(value, '0', sizeof(value));
+  nv.value = value;
+  nv.valuelen = sizeof(value);
+
+  nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+  CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 14, &nv, 1));
+  CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 15, &nv, 1));
+  CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 16, &nv, 1));
+  CU_ASSERT(0 == nghttp2_hd_emit_indname_block(&bufs, 17, &nv, 1));
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(blocklen > 0);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(4 == out.nvlen);
+  CU_ASSERT(14 == out.nva[0].namelen);
+  CU_ASSERT(0 == memcmp("accept-charset", out.nva[0].name, out.nva[0].namelen));
+  CU_ASSERT(sizeof(value) == out.nva[0].valuelen);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  CU_ASSERT(3 == inflater.ctx.hd_table.len);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_newname_noinc(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nv[] = {/* Expecting huffman for both */
+                     MAKE_NV("my-long-content-length", "nghttp2"),
+                     /* Expecting no huffman for both */
+                     MAKE_NV("x", "y"),
+                     /* Huffman for key only */
+                     MAKE_NV("my-long-content-length", "y"),
+                     /* Huffman for value only */
+                     MAKE_NV("x", "nghttp2")};
+  size_t i;
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_inflate_init(&inflater, mem);
+  for (i = 0; i < ARRLEN(nv); ++i) {
+    CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv[i], 0));
+
+    blocklen = nghttp2_bufs_len(&bufs);
+
+    CU_ASSERT(blocklen > 0);
+    CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+    CU_ASSERT(1 == out.nvlen);
+    assert_nv_equal(&nv[i], out.nva, 1);
+    CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+    nva_out_reset(&out);
+    nghttp2_bufs_reset(&bufs);
+  }
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_newname_inc(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nv = MAKE_NV("x-rel", "nghttp2");
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv, 1));
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+  assert_nv_equal(&nv, out.nva, 1);
+  CU_ASSERT(1 == inflater.ctx.hd_table.len);
+  assert_nv_equal(
+      &nv, &GET_TABLE_ENT(&inflater.ctx, NGHTTP2_STATIC_TABLE_LENGTH +
+                                             inflater.ctx.hd_table.len - 1)->nv,
+      1);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_clearall_inc(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nv;
+  uint8_t value[4060];
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  bufs_large_init(&bufs, 8192);
+
+  nva_out_init(&out);
+  /* Total 4097 bytes space required to hold this entry */
+  nv.name = (uint8_t *)"alpha";
+  nv.namelen = strlen((char *)nv.name);
+  memset(value, '0', sizeof(value));
+  nv.value = value;
+  nv.valuelen = sizeof(value);
+
+  nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv, 1));
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+  assert_nv_equal(&nv, out.nva, 1);
+  CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+  nva_out_reset(&out);
+
+  /* Do it again */
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+  assert_nv_equal(&nv, out.nva, 1);
+  CU_ASSERT(0 == inflater.ctx.hd_table.len);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* This time, 4096 bytes space required, which is just fits in the
+     header table */
+  nv.valuelen = sizeof(value) - 1;
+
+  CU_ASSERT(0 == nghttp2_hd_emit_newname_block(&bufs, &nv, 1));
+
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+  assert_nv_equal(&nv, out.nva, 1);
+  CU_ASSERT(1 == inflater.ctx.hd_table.len);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_inflate_zero_length_huffman(void) {
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  /* Literal header without indexing - new name */
+  uint8_t data[] = {0x40, 0x01, 0x78 /* 'x' */, 0x80};
+  nva_out out;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+
+  nghttp2_bufs_add(&bufs, data, sizeof(data));
+
+  /* /\* Literal header without indexing - new name *\/ */
+  /* ptr[0] = 0x40; */
+  /* ptr[1] = 1; */
+  /* ptr[2] = 'x'; */
+  /* ptr[3] = 0x80; */
+
+  nghttp2_hd_inflate_init(&inflater, mem);
+  CU_ASSERT(4 == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(1 == out.nvlen);
+  CU_ASSERT(1 == out.nva[0].namelen);
+  CU_ASSERT('x' == out.nva[0].name[0]);
+  CU_ASSERT(NULL == out.nva[0].value);
+  CU_ASSERT(0 == out.nva[0].valuelen);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+}
+
+void test_nghttp2_hd_ringbuf_reserve(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_nv nv;
+  nghttp2_bufs bufs;
+  nva_out out;
+  int i;
+  ssize_t rv;
+  ssize_t blocklen;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+  nva_out_init(&out);
+
+  nv.flags = NGHTTP2_NV_FLAG_NONE;
+  nv.name = (uint8_t *)"a";
+  nv.namelen = strlen((const char *)nv.name);
+  nv.valuelen = 4;
+  nv.value = malloc(nv.valuelen);
+  memset(nv.value, 0, nv.valuelen);
+
+  nghttp2_hd_deflate_init2(&deflater, 8000, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  nghttp2_hd_inflate_change_table_size(&inflater, 8000);
+  nghttp2_hd_deflate_change_table_size(&deflater, 8000);
+
+  for (i = 0; i < 150; ++i) {
+    memcpy(nv.value, &i, sizeof(i));
+    rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, &nv, 1);
+    blocklen = nghttp2_bufs_len(&bufs);
+
+    CU_ASSERT(0 == rv);
+    CU_ASSERT(blocklen > 0);
+
+    CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+    CU_ASSERT(1 == out.nvlen);
+    assert_nv_equal(&nv, out.nva, 1);
+
+    nva_out_reset(&out);
+    nghttp2_bufs_reset(&bufs);
+  }
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+
+  free(nv.value);
+}
+
+void test_nghttp2_hd_change_table_size(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_nv nva[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")};
+  nghttp2_nv nva2[] = {MAKE_NV(":path", "/")};
+  nghttp2_bufs bufs;
+  ssize_t rv;
+  nva_out out;
+  ssize_t blocklen;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  /* inflater changes notifies 8000 max header table size */
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 8000));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 8000));
+
+  CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max);
+
+  /* This will emit encoding context update with header table size 4096 */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(2 == deflater.ctx.hd_table.len);
+  CU_ASSERT(4096 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(2 == inflater.ctx.hd_table.len);
+  CU_ASSERT(4096 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* inflater changes header table size to 1024 */
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 1024));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 1024));
+
+  CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max);
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(2 == deflater.ctx.hd_table.len);
+  CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(2 == inflater.ctx.hd_table.len);
+  CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(1024 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* inflater changes header table size to 0 */
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0));
+
+  CU_ASSERT(0 == deflater.ctx.hd_table.len);
+  CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(0 == inflater.ctx.hd_table.len);
+  CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max);
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(0 == deflater.ctx.hd_table.len);
+  CU_ASSERT(0 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(0 == inflater.ctx.hd_table.len);
+  CU_ASSERT(0 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(0 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+
+  /* Check table buffer is expanded */
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_hd_deflate_init2(&deflater, 8192, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  /* First inflater changes header table size to 8000 */
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 8000));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 8000));
+
+  CU_ASSERT(8000 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max);
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(2 == deflater.ctx.hd_table.len);
+  CU_ASSERT(8000 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(2 == inflater.ctx.hd_table.len);
+  CU_ASSERT(8000 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(8000 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 16383));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 16383));
+
+  CU_ASSERT(8192 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(16383 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max);
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(2 == deflater.ctx.hd_table.len);
+  CU_ASSERT(8192 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(2 == inflater.ctx.hd_table.len);
+  CU_ASSERT(8192 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(16383 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  /* Lastly, check the error condition */
+
+  rv = nghttp2_hd_emit_table_size(&bufs, 25600);
+  CU_ASSERT(rv == 0);
+  CU_ASSERT(NGHTTP2_ERR_HEADER_COMP == inflate_hd(&inflater, &out, &bufs, 0));
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+
+  /* Check that encoder can handle the case where its allowable buffer
+     size is less than default size, 4096 */
+  nghttp2_hd_deflate_init2(&deflater, 1024, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max);
+
+  /* This emits context update with buffer size 1024 */
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(2 == deflater.ctx.hd_table.len);
+  CU_ASSERT(1024 == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(2 == inflater.ctx.hd_table.len);
+  CU_ASSERT(1024 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(4096 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+
+  /* Check that table size UINT32_MAX can be received */
+  nghttp2_hd_deflate_init2(&deflater, UINT32_MAX, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, UINT32_MAX));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, UINT32_MAX));
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, 2);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(UINT32_MAX == deflater.ctx.hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(UINT32_MAX == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(UINT32_MAX == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+
+  /* Check that context update emitted twice */
+  nghttp2_hd_deflate_init2(&deflater, 4096, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 0));
+  CU_ASSERT(0 == nghttp2_hd_inflate_change_table_size(&inflater, 3000));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 0));
+  CU_ASSERT(0 == nghttp2_hd_deflate_change_table_size(&deflater, 3000));
+
+  CU_ASSERT(0 == deflater.min_hd_table_bufsize_max);
+  CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max);
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva2, 1);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(3 < blocklen);
+  CU_ASSERT(3000 == deflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(UINT32_MAX == deflater.min_hd_table_bufsize_max);
+
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+  CU_ASSERT(3000 == inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(3000 == inflater.settings_hd_table_bufsize_max);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+
+  nghttp2_bufs_free(&bufs);
+}
+
+static void check_deflate_inflate(nghttp2_hd_deflater *deflater,
+                                  nghttp2_hd_inflater *inflater,
+                                  nghttp2_nv *nva, size_t nvlen) {
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nva_out out;
+  int rv;
+
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  rv = nghttp2_hd_deflate_hd_bufs(deflater, &bufs, nva, nvlen);
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen >= 0);
+
+  CU_ASSERT(blocklen == inflate_hd(inflater, &out, &bufs, 0));
+
+  CU_ASSERT(nvlen == out.nvlen);
+  assert_nv_equal(nva, out.nva, nvlen);
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+}
+
+void test_nghttp2_hd_deflate_inflate(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_nv nv1[] = {
+      MAKE_NV(":status", "200 OK"),
+      MAKE_NV("access-control-allow-origin", "*"),
+      MAKE_NV("cache-control", "private, max-age=0, must-revalidate"),
+      MAKE_NV("content-length", "76073"),
+      MAKE_NV("content-type", "text/html"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("server", "Apache"),
+      MAKE_NV("vary", "foobar"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "MISS from alphabravo"),
+      MAKE_NV("x-cache-action", "MISS"),
+      MAKE_NV("x-cache-age", "0"),
+      MAKE_NV("x-cache-lookup", "MISS from alphabravo:3128"),
+      MAKE_NV("x-lb-nocache", "true"),
+  };
+  nghttp2_nv nv2[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=56682045"),
+      MAKE_NV("content-type", "text/css"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Thu, 14 May 2015 07:22:57 GMT"),
+      MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:15 GMT"),
+      MAKE_NV("vary", "Accept-Encoding"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128")};
+  nghttp2_nv nv3[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=56682072"),
+      MAKE_NV("content-type", "text/css"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Thu, 14 May 2015 07:23:24 GMT"),
+      MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:13 GMT"),
+      MAKE_NV("vary", "Accept-Encoding"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv4[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=56682022"),
+      MAKE_NV("content-type", "text/css"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Thu, 14 May 2015 07:22:34 GMT"),
+      MAKE_NV("last-modified", "Tue, 14 May 2013 07:22:14 GMT"),
+      MAKE_NV("vary", "Accept-Encoding"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv5[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=4461139"),
+      MAKE_NV("content-type", "application/x-javascript"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Mon, 16 Sep 2013 21:34:31 GMT"),
+      MAKE_NV("last-modified", "Thu, 05 May 2011 09:15:59 GMT"),
+      MAKE_NV("vary", "Accept-Encoding"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv6[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=18645951"),
+      MAKE_NV("content-type", "application/x-javascript"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Fri, 28 Feb 2014 01:48:03 GMT"),
+      MAKE_NV("last-modified", "Tue, 12 Jul 2011 16:02:59 GMT"),
+      MAKE_NV("vary", "Accept-Encoding"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv7[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=31536000"),
+      MAKE_NV("content-type", "application/javascript"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("etag", "\"6807-4dc5b54e0dcc0\""),
+      MAKE_NV("expires", "Wed, 21 May 2014 08:32:17 GMT"),
+      MAKE_NV("last-modified", "Fri, 10 May 2013 11:18:51 GMT"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv8[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=31536000"),
+      MAKE_NV("content-type", "application/javascript"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("etag", "\"41c6-4de7d28585b00\""),
+      MAKE_NV("expires", "Thu, 12 Jun 2014 10:00:58 GMT"),
+      MAKE_NV("last-modified", "Thu, 06 Jun 2013 14:30:36 GMT"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv9[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=31536000"),
+      MAKE_NV("content-type", "application/javascript"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("etag", "\"19d6e-4dc5b35a541c0\""),
+      MAKE_NV("expires", "Wed, 21 May 2014 08:32:18 GMT"),
+      MAKE_NV("last-modified", "Fri, 10 May 2013 11:10:07 GMT"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_nv nv10[] = {
+      MAKE_NV(":status", "304 Not Modified"),
+      MAKE_NV("age", "0"),
+      MAKE_NV("cache-control", "max-age=56682045"),
+      MAKE_NV("content-type", "text/css"),
+      MAKE_NV("date", "Sat, 27 Jul 2013 06:22:12 GMT"),
+      MAKE_NV("expires", "Thu, 14 May 2015 07:22:57 GMT"),
+      MAKE_NV("last-modified", "Tue, 14 May 2013 07:21:53 GMT"),
+      MAKE_NV("vary", "Accept-Encoding"),
+      MAKE_NV("via", "1.1 alphabravo (squid/3.x.x), 1.1 nghttpx"),
+      MAKE_NV("x-cache", "HIT from alphabravo"),
+      MAKE_NV("x-cache-lookup", "HIT from alphabravo:3128"),
+  };
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  check_deflate_inflate(&deflater, &inflater, nv1, ARRLEN(nv1));
+  check_deflate_inflate(&deflater, &inflater, nv2, ARRLEN(nv2));
+  check_deflate_inflate(&deflater, &inflater, nv3, ARRLEN(nv3));
+  check_deflate_inflate(&deflater, &inflater, nv4, ARRLEN(nv4));
+  check_deflate_inflate(&deflater, &inflater, nv5, ARRLEN(nv5));
+  check_deflate_inflate(&deflater, &inflater, nv6, ARRLEN(nv6));
+  check_deflate_inflate(&deflater, &inflater, nv7, ARRLEN(nv7));
+  check_deflate_inflate(&deflater, &inflater, nv8, ARRLEN(nv8));
+  check_deflate_inflate(&deflater, &inflater, nv9, ARRLEN(nv9));
+  check_deflate_inflate(&deflater, &inflater, nv10, ARRLEN(nv10));
+
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_hd_no_index(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_hd_inflater inflater;
+  nghttp2_bufs bufs;
+  ssize_t blocklen;
+  nghttp2_nv nva[] = {
+      MAKE_NV(":method", "GET"), MAKE_NV(":method", "POST"),
+      MAKE_NV(":path", "/foo"),  MAKE_NV("version", "HTTP/1.1"),
+      MAKE_NV(":method", "GET"),
+  };
+  size_t i;
+  nva_out out;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  /* 1st :method: GET can be indexable, last one is not */
+  for (i = 1; i < ARRLEN(nva); ++i) {
+    nva[i].flags = NGHTTP2_NV_FLAG_NO_INDEX;
+  }
+
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nghttp2_hd_inflate_init(&inflater, mem);
+
+  rv = nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, ARRLEN(nva));
+  blocklen = nghttp2_bufs_len(&bufs);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(blocklen > 0);
+  CU_ASSERT(blocklen == inflate_hd(&inflater, &out, &bufs, 0));
+
+  CU_ASSERT(ARRLEN(nva) == out.nvlen);
+  assert_nv_equal(nva, out.nva, ARRLEN(nva));
+
+  CU_ASSERT(out.nva[0].flags == NGHTTP2_NV_FLAG_NONE);
+  for (i = 1; i < ARRLEN(nva); ++i) {
+    CU_ASSERT(out.nva[i].flags == NGHTTP2_NV_FLAG_NO_INDEX);
+  }
+
+  nva_out_reset(&out);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_hd_deflate_bound(void) {
+  nghttp2_hd_deflater deflater;
+  nghttp2_nv nva[] = {MAKE_NV(":method", "GET"), MAKE_NV("alpha", "bravo")};
+  nghttp2_bufs bufs;
+  size_t bound, bound2;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  bound = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva));
+
+  CU_ASSERT(12 + 6 * 2 * 2 + nva[0].namelen + nva[0].valuelen + nva[1].namelen +
+                nva[1].valuelen ==
+            bound);
+
+  nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, nva, ARRLEN(nva));
+
+  CU_ASSERT(bound > (size_t)nghttp2_bufs_len(&bufs));
+
+  bound2 = nghttp2_hd_deflate_bound(&deflater, nva, ARRLEN(nva));
+
+  CU_ASSERT(bound == bound2);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_deflate_free(&deflater);
+}
+
+void test_nghttp2_hd_public_api(void) {
+  nghttp2_hd_deflater *deflater;
+  nghttp2_hd_inflater *inflater;
+  nghttp2_nv nva[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")};
+  uint8_t buf[4096];
+  size_t buflen;
+  ssize_t blocklen;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  CU_ASSERT(0 == nghttp2_hd_deflate_new(&deflater, 4096));
+  CU_ASSERT(0 == nghttp2_hd_inflate_new(&inflater));
+
+  buflen = nghttp2_hd_deflate_bound(deflater, nva, ARRLEN(nva));
+
+  blocklen = nghttp2_hd_deflate_hd(deflater, buf, buflen, nva, ARRLEN(nva));
+
+  CU_ASSERT(blocklen > 0);
+
+  nghttp2_bufs_wrap_init(&bufs, buf, blocklen, mem);
+  bufs.head->buf.last += blocklen;
+
+  CU_ASSERT(blocklen == inflate_hd(inflater, NULL, &bufs, 0));
+
+  nghttp2_bufs_wrap_free(&bufs);
+
+  nghttp2_hd_inflate_del(inflater);
+  nghttp2_hd_deflate_del(deflater);
+
+  /* See NGHTTP2_ERR_INSUFF_BUFSIZE */
+  CU_ASSERT(0 == nghttp2_hd_deflate_new(&deflater, 4096));
+
+  blocklen =
+      nghttp2_hd_deflate_hd(deflater, buf, blocklen - 1, nva, ARRLEN(nva));
+
+  CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == blocklen);
+
+  nghttp2_hd_deflate_del(deflater);
+}
+
+static size_t encode_length(uint8_t *buf, uint64_t n, size_t prefix) {
+  size_t k = (1 << prefix) - 1;
+  size_t len = 0;
+  *buf &= ~k;
+  if (n >= k) {
+    *buf++ |= k;
+    n -= k;
+    ++len;
+  } else {
+    *buf++ |= n;
+    return 1;
+  }
+  do {
+    ++len;
+    if (n >= 128) {
+      *buf++ = (1 << 7) | (n & 0x7f);
+      n >>= 7;
+    } else {
+      *buf++ = (uint8_t)n;
+      break;
+    }
+  } while (n);
+  return len;
+}
+
+void test_nghttp2_hd_decode_length(void) {
+  uint32_t out;
+  size_t shift;
+  int final;
+  uint8_t buf[16];
+  uint8_t *bufp;
+  size_t len;
+  ssize_t rv;
+  size_t i;
+
+  memset(buf, 0, sizeof(buf));
+  len = encode_length(buf, UINT32_MAX, 7);
+
+  rv = nghttp2_hd_decode_length(&out, &shift, &final, 0, 0, buf, buf + len, 7);
+
+  CU_ASSERT((ssize_t)len == rv);
+  CU_ASSERT(0 != final);
+  CU_ASSERT(UINT32_MAX == out);
+
+  /* Make sure that we can decode integer if we feed 1 byte at a
+     time */
+  out = 0;
+  shift = 0;
+  final = 0;
+  bufp = buf;
+
+  for (i = 0; i < len; ++i, ++bufp) {
+    rv = nghttp2_hd_decode_length(&out, &shift, &final, out, shift, bufp,
+                                  bufp + 1, 7);
+
+    CU_ASSERT(rv == 1);
+
+    if (final) {
+      break;
+    }
+  }
+
+  CU_ASSERT(i == len - 1);
+  CU_ASSERT(0 != final);
+  CU_ASSERT(UINT32_MAX == out);
+
+  /* Check overflow case */
+  memset(buf, 0, sizeof(buf));
+  len = encode_length(buf, 1ll << 32, 7);
+
+  rv = nghttp2_hd_decode_length(&out, &shift, &final, 0, 0, buf, buf + len, 7);
+
+  CU_ASSERT(-1 == rv);
+}
+
+void test_nghttp2_hd_huff_encode(void) {
+  int rv;
+  ssize_t len;
+  nghttp2_bufs bufs, outbufs;
+  nghttp2_hd_huff_decode_context ctx;
+  const uint8_t t1[] = {22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11,
+                        10, 9,  8,  7,  6,  5,  4,  3,  2,  1,  0};
+
+  frame_pack_bufs_init(&bufs);
+  frame_pack_bufs_init(&outbufs);
+
+  rv = nghttp2_hd_huff_encode(&bufs, t1, sizeof(t1));
+
+  CU_ASSERT(rv == 0);
+
+  nghttp2_hd_huff_decode_context_init(&ctx);
+
+  len = nghttp2_hd_huff_decode(&ctx, &outbufs, bufs.cur->buf.pos,
+                               nghttp2_bufs_len(&bufs), 1);
+
+  CU_ASSERT(nghttp2_bufs_len(&bufs) == len);
+  CU_ASSERT((ssize_t)sizeof(t1) == nghttp2_bufs_len(&outbufs));
+
+  CU_ASSERT(0 == memcmp(t1, outbufs.cur->buf.pos, sizeof(t1)));
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_bufs_free(&outbufs);
+}
diff --git a/tests/nghttp2_hd_test.h b/tests/nghttp2_hd_test.h
new file mode 100644 (file)
index 0000000..e41fad7
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_HD_TEST_H
+#define NGHTTP2_HD_TEST_H
+
+void test_nghttp2_hd_deflate(void);
+void test_nghttp2_hd_deflate_same_indexed_repr(void);
+void test_nghttp2_hd_inflate_indexed(void);
+void test_nghttp2_hd_inflate_indname_noinc(void);
+void test_nghttp2_hd_inflate_indname_inc(void);
+void test_nghttp2_hd_inflate_indname_inc_eviction(void);
+void test_nghttp2_hd_inflate_newname_noinc(void);
+void test_nghttp2_hd_inflate_newname_inc(void);
+void test_nghttp2_hd_inflate_clearall_inc(void);
+void test_nghttp2_hd_inflate_zero_length_huffman(void);
+void test_nghttp2_hd_ringbuf_reserve(void);
+void test_nghttp2_hd_change_table_size(void);
+void test_nghttp2_hd_deflate_inflate(void);
+void test_nghttp2_hd_no_index(void);
+void test_nghttp2_hd_deflate_bound(void);
+void test_nghttp2_hd_public_api(void);
+void test_nghttp2_hd_decode_length(void);
+void test_nghttp2_hd_huff_encode(void);
+
+#endif /* NGHTTP2_HD_TEST_H */
diff --git a/tests/nghttp2_helper_test.c b/tests/nghttp2_helper_test.c
new file mode 100644 (file)
index 0000000..b29e67b
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_helper_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_helper.h"
+
+void test_nghttp2_adjust_local_window_size(void) {
+  int32_t local_window_size = 100;
+  int32_t recv_window_size = 50;
+  int32_t recv_reduction = 0;
+  int32_t delta;
+
+  delta = 0;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(100 == local_window_size);
+  CU_ASSERT(50 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(0 == delta);
+
+  delta = 49;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(100 == local_window_size);
+  CU_ASSERT(1 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(49 == delta);
+
+  delta = 1;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(100 == local_window_size);
+  CU_ASSERT(0 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(1 == delta);
+
+  delta = 1;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(101 == local_window_size);
+  CU_ASSERT(0 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(1 == delta);
+
+  delta = -1;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(100 == local_window_size);
+  CU_ASSERT(-1 == recv_window_size);
+  CU_ASSERT(1 == recv_reduction);
+  CU_ASSERT(0 == delta);
+
+  delta = 1;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(101 == local_window_size);
+  CU_ASSERT(0 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(0 == delta);
+
+  delta = 100;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(201 == local_window_size);
+  CU_ASSERT(0 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(100 == delta);
+
+  delta = -3;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(198 == local_window_size);
+  CU_ASSERT(-3 == recv_window_size);
+  CU_ASSERT(3 == recv_reduction);
+  CU_ASSERT(0 == delta);
+
+  recv_window_size += 3;
+
+  delta = 3;
+  CU_ASSERT(0 == nghttp2_adjust_local_window_size(&local_window_size,
+                                                  &recv_window_size,
+                                                  &recv_reduction, &delta));
+  CU_ASSERT(201 == local_window_size);
+  CU_ASSERT(3 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(0 == delta);
+
+  local_window_size = 100;
+  recv_window_size = 50;
+  recv_reduction = 0;
+  delta = INT32_MAX;
+  CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
+            nghttp2_adjust_local_window_size(&local_window_size,
+                                             &recv_window_size, &recv_reduction,
+                                             &delta));
+  CU_ASSERT(100 == local_window_size);
+  CU_ASSERT(50 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(INT32_MAX == delta);
+
+  delta = INT32_MIN;
+  CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
+            nghttp2_adjust_local_window_size(&local_window_size,
+                                             &recv_window_size, &recv_reduction,
+                                             &delta));
+  CU_ASSERT(100 == local_window_size);
+  CU_ASSERT(50 == recv_window_size);
+  CU_ASSERT(0 == recv_reduction);
+  CU_ASSERT(INT32_MIN == delta);
+}
+
+#define check_header_name(S)                                                   \
+  nghttp2_check_header_name((const uint8_t *)S, sizeof(S) - 1)
+
+void test_nghttp2_check_header_name(void) {
+  CU_ASSERT(check_header_name(":path"));
+  CU_ASSERT(check_header_name("path"));
+  CU_ASSERT(check_header_name("!#$%&'*+-.^_`|~"));
+  CU_ASSERT(!check_header_name(":PATH"));
+  CU_ASSERT(!check_header_name("path:"));
+  CU_ASSERT(!check_header_name(""));
+  CU_ASSERT(!check_header_name(":"));
+}
+
+#define check_header_value(S)                                                  \
+  nghttp2_check_header_value((const uint8_t *)S, sizeof(S) - 1)
+
+void test_nghttp2_check_header_value(void) {
+  uint8_t goodval[] = {'a', 'b', 0x80u, 'c', 0xffu, 'd', '\t', ' '};
+  uint8_t badval1[] = {'a', 0x1fu, 'b'};
+  uint8_t badval2[] = {'a', 0x7fu, 'b'};
+
+  CU_ASSERT(check_header_value(" !|}~"));
+  CU_ASSERT(check_header_value(goodval));
+  CU_ASSERT(!check_header_value(badval1));
+  CU_ASSERT(!check_header_value(badval2));
+}
diff --git a/tests/nghttp2_helper_test.h b/tests/nghttp2_helper_test.h
new file mode 100644 (file)
index 0000000..173fde6
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_HELPER_TEST_H
+#define NGHTTP2_HELPER_TEST_H
+
+void test_nghttp2_adjust_local_window_size(void);
+void test_nghttp2_check_header_name(void);
+void test_nghttp2_check_header_value(void);
+
+#endif /* NGHTTP2_HELPER_TEST_H */
diff --git a/tests/nghttp2_map_test.c b/tests/nghttp2_map_test.c
new file mode 100644 (file)
index 0000000..b244b91
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_map_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_map.h"
+
+typedef struct strentry {
+  nghttp2_map_entry map_entry;
+  const char *str;
+} strentry;
+
+static void strentry_init(strentry *entry, key_type key, const char *str) {
+  nghttp2_map_entry_init(&entry->map_entry, key);
+  entry->str = str;
+}
+
+void test_nghttp2_map(void) {
+  strentry foo, FOO, bar, baz, shrubbery;
+  nghttp2_map map;
+  nghttp2_map_init(&map, nghttp2_mem_default());
+
+  strentry_init(&foo, 1, "foo");
+  strentry_init(&FOO, 1, "FOO");
+  strentry_init(&bar, 2, "bar");
+  strentry_init(&baz, 3, "baz");
+  strentry_init(&shrubbery, 4, "shrubbery");
+
+  CU_ASSERT(0 == nghttp2_map_insert(&map, &foo.map_entry));
+  CU_ASSERT(strcmp("foo", ((strentry *)nghttp2_map_find(&map, 1))->str) == 0);
+  CU_ASSERT(1 == nghttp2_map_size(&map));
+
+  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+            nghttp2_map_insert(&map, &FOO.map_entry));
+
+  CU_ASSERT(1 == nghttp2_map_size(&map));
+  CU_ASSERT(strcmp("foo", ((strentry *)nghttp2_map_find(&map, 1))->str) == 0);
+
+  CU_ASSERT(0 == nghttp2_map_insert(&map, &bar.map_entry));
+  CU_ASSERT(2 == nghttp2_map_size(&map));
+
+  CU_ASSERT(0 == nghttp2_map_insert(&map, &baz.map_entry));
+  CU_ASSERT(3 == nghttp2_map_size(&map));
+
+  CU_ASSERT(0 == nghttp2_map_insert(&map, &shrubbery.map_entry));
+  CU_ASSERT(4 == nghttp2_map_size(&map));
+
+  CU_ASSERT(strcmp("baz", ((strentry *)nghttp2_map_find(&map, 3))->str) == 0);
+
+  nghttp2_map_remove(&map, 3);
+  CU_ASSERT(3 == nghttp2_map_size(&map));
+  CU_ASSERT(NULL == nghttp2_map_find(&map, 3));
+
+  nghttp2_map_remove(&map, 1);
+  CU_ASSERT(2 == nghttp2_map_size(&map));
+  CU_ASSERT(NULL == nghttp2_map_find(&map, 1));
+
+  /* Erasing non-existent entry */
+  nghttp2_map_remove(&map, 1);
+  CU_ASSERT(2 == nghttp2_map_size(&map));
+  CU_ASSERT(NULL == nghttp2_map_find(&map, 1));
+
+  CU_ASSERT(strcmp("bar", ((strentry *)nghttp2_map_find(&map, 2))->str) == 0);
+  CU_ASSERT(strcmp("shrubbery", ((strentry *)nghttp2_map_find(&map, 4))->str) ==
+            0);
+
+  nghttp2_map_free(&map);
+}
+
+static void shuffle(int *a, int n) {
+  int i;
+  for (i = n - 1; i >= 1; --i) {
+    size_t j = (int)((double)(i + 1) * rand() / (RAND_MAX + 1.0));
+    int t = a[j];
+    a[j] = a[i];
+    a[i] = t;
+  }
+}
+
+static int eachfun(nghttp2_map_entry *entry _U_, void *ptr _U_) { return 0; }
+
+#define NUM_ENT 6000
+strentry arr[NUM_ENT];
+int order[NUM_ENT];
+
+void test_nghttp2_map_functional(void) {
+  nghttp2_map map;
+  int i;
+
+  nghttp2_map_init(&map, nghttp2_mem_default());
+  for (i = 0; i < NUM_ENT; ++i) {
+    strentry_init(&arr[i], i + 1, "foo");
+    order[i] = i + 1;
+  }
+  /* insertion */
+  shuffle(order, NUM_ENT);
+  for (i = 0; i < NUM_ENT; ++i) {
+    CU_ASSERT(0 == nghttp2_map_insert(&map, &arr[order[i] - 1].map_entry));
+  }
+  /* traverse */
+  nghttp2_map_each(&map, eachfun, NULL);
+  /* find */
+  shuffle(order, NUM_ENT);
+  for (i = 0; i < NUM_ENT; ++i) {
+    nghttp2_map_find(&map, order[i]);
+  }
+  /* remove */
+  shuffle(order, NUM_ENT);
+  for (i = 0; i < NUM_ENT; ++i) {
+    CU_ASSERT(0 == nghttp2_map_remove(&map, order[i]));
+  }
+
+  /* each_free (but no op function for testing purpose) */
+  for (i = 0; i < NUM_ENT; ++i) {
+    strentry_init(&arr[i], i + 1, "foo");
+  }
+  /* insert once again */
+  for (i = 0; i < NUM_ENT; ++i) {
+    CU_ASSERT(0 == nghttp2_map_insert(&map, &arr[i].map_entry));
+  }
+  nghttp2_map_each_free(&map, eachfun, NULL);
+  nghttp2_map_free(&map);
+}
+
+static int entry_free(nghttp2_map_entry *entry, void *ptr _U_) {
+  free(entry);
+  return 0;
+}
+
+void test_nghttp2_map_each_free(void) {
+  strentry *foo = malloc(sizeof(strentry)), *bar = malloc(sizeof(strentry)),
+           *baz = malloc(sizeof(strentry)),
+           *shrubbery = malloc(sizeof(strentry));
+  nghttp2_map map;
+  nghttp2_map_init(&map, nghttp2_mem_default());
+
+  strentry_init(foo, 1, "foo");
+  strentry_init(bar, 2, "bar");
+  strentry_init(baz, 3, "baz");
+  strentry_init(shrubbery, 4, "shrubbery");
+
+  nghttp2_map_insert(&map, &foo->map_entry);
+  nghttp2_map_insert(&map, &bar->map_entry);
+  nghttp2_map_insert(&map, &baz->map_entry);
+  nghttp2_map_insert(&map, &shrubbery->map_entry);
+
+  nghttp2_map_each_free(&map, entry_free, NULL);
+  nghttp2_map_free(&map);
+}
diff --git a/tests/nghttp2_map_test.h b/tests/nghttp2_map_test.h
new file mode 100644 (file)
index 0000000..f0b723b
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_MAP_TEST_H
+#define NGHTTP2_MAP_TEST_H
+
+void test_nghttp2_map(void);
+void test_nghttp2_map_functional(void);
+void test_nghttp2_map_each_free(void);
+
+#endif /* NGHTTP2_MAP_TEST_H */
diff --git a/tests/nghttp2_npn_test.c b/tests/nghttp2_npn_test.c
new file mode 100644 (file)
index 0000000..bae6082
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Twist Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_npn_test.h"
+
+#include <string.h>
+
+#include <CUnit/CUnit.h>
+#include <nghttp2/nghttp2.h>
+
+static void http2(void) {
+  const unsigned char p[] = {8,   'h', 't', 't', 'p', '/', '1', '.',
+                             '1', 5,   'h', '2', '-', '1', '4', 6,
+                             's', 'p', 'd', 'y', '/', '3'};
+  unsigned char outlen;
+  unsigned char *out;
+  CU_ASSERT(1 == nghttp2_select_next_protocol(&out, &outlen, p, sizeof(p)));
+  CU_ASSERT(NGHTTP2_PROTO_VERSION_ID_LEN == outlen);
+  CU_ASSERT(memcmp(NGHTTP2_PROTO_VERSION_ID, out, outlen) == 0);
+}
+
+static void http11(void) {
+  const unsigned char spdy[] = {
+      6,   's', 'p', 'd', 'y', '/', '4', 8,   's', 'p', 'd', 'y', '/',
+      '2', '.', '1', 8,   'h', 't', 't', 'p', '/', '1', '.', '1',
+  };
+  unsigned char outlen;
+  unsigned char *out;
+  CU_ASSERT(0 ==
+            nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy)));
+  CU_ASSERT(8 == outlen);
+  CU_ASSERT(memcmp("http/1.1", out, outlen) == 0);
+}
+
+static void no_overlap(void) {
+  const unsigned char spdy[] = {
+      6,   's', 'p', 'd', 'y', '/', '4', 8,   's', 'p', 'd', 'y', '/',
+      '2', '.', '1', 8,   'h', 't', 't', 'p', '/', '1', '.', '0',
+  };
+  unsigned char outlen = 0;
+  unsigned char *out = NULL;
+  CU_ASSERT(-1 ==
+            nghttp2_select_next_protocol(&out, &outlen, spdy, sizeof(spdy)));
+  CU_ASSERT(0 == outlen);
+  CU_ASSERT(NULL == out);
+}
+
+void test_nghttp2_npn(void) {
+  http2();
+  http11();
+  no_overlap();
+}
diff --git a/tests/nghttp2_npn_test.h b/tests/nghttp2_npn_test.h
new file mode 100644 (file)
index 0000000..e806f3a
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Twist Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_NPN_TEST_H
+#define NGHTTP2_NPN_TEST_H
+
+void test_nghttp2_npn(void);
+
+#endif /* NGHTTP2_NPN_TEST_H */
diff --git a/tests/nghttp2_pq_test.c b/tests/nghttp2_pq_test.c
new file mode 100644 (file)
index 0000000..1cef4d0
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_pq_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_pq.h"
+
+static int pq_compar(const void *lhs, const void *rhs) {
+  return strcmp(lhs, rhs);
+}
+
+void test_nghttp2_pq(void) {
+  int i;
+  nghttp2_pq pq;
+  nghttp2_pq_init(&pq, pq_compar, nghttp2_mem_default());
+  CU_ASSERT(nghttp2_pq_empty(&pq));
+  CU_ASSERT(0 == nghttp2_pq_size(&pq));
+  CU_ASSERT(0 == nghttp2_pq_push(&pq, (void *)"foo"));
+  CU_ASSERT(0 == nghttp2_pq_empty(&pq));
+  CU_ASSERT(1 == nghttp2_pq_size(&pq));
+  CU_ASSERT(strcmp("foo", nghttp2_pq_top(&pq)) == 0);
+  CU_ASSERT(0 == nghttp2_pq_push(&pq, (void *)"bar"));
+  CU_ASSERT(strcmp("bar", nghttp2_pq_top(&pq)) == 0);
+  CU_ASSERT(0 == nghttp2_pq_push(&pq, (void *)"baz"));
+  CU_ASSERT(strcmp("bar", nghttp2_pq_top(&pq)) == 0);
+  CU_ASSERT(0 == nghttp2_pq_push(&pq, (void *)"C"));
+  CU_ASSERT(4 == nghttp2_pq_size(&pq));
+  CU_ASSERT(strcmp("C", nghttp2_pq_top(&pq)) == 0);
+  nghttp2_pq_pop(&pq);
+  CU_ASSERT(3 == nghttp2_pq_size(&pq));
+  CU_ASSERT(strcmp("bar", nghttp2_pq_top(&pq)) == 0);
+  nghttp2_pq_pop(&pq);
+  CU_ASSERT(strcmp("baz", nghttp2_pq_top(&pq)) == 0);
+  nghttp2_pq_pop(&pq);
+  CU_ASSERT(strcmp("foo", nghttp2_pq_top(&pq)) == 0);
+  nghttp2_pq_pop(&pq);
+  CU_ASSERT(nghttp2_pq_empty(&pq));
+  CU_ASSERT(0 == nghttp2_pq_size(&pq));
+  CU_ASSERT(NULL == nghttp2_pq_top(&pq));
+
+  /* Add bunch of entry to see realloc works */
+  for (i = 0; i < 10000; ++i) {
+    CU_ASSERT(0 == nghttp2_pq_push(&pq, (void *)"foo"));
+    CU_ASSERT((size_t)(i + 1) == nghttp2_pq_size(&pq));
+  }
+  for (i = 10000; i > 0; --i) {
+    CU_ASSERT(NULL != nghttp2_pq_top(&pq));
+    nghttp2_pq_pop(&pq);
+    CU_ASSERT((size_t)(i - 1) == nghttp2_pq_size(&pq));
+  }
+
+  nghttp2_pq_free(&pq);
+}
+
+typedef struct {
+  int key;
+  int val;
+} node;
+
+static int node_compar(const void *lhs, const void *rhs) {
+  node *ln = (node *)lhs;
+  node *rn = (node *)rhs;
+  return ln->key - rn->key;
+}
+
+static int node_update(void *item, void *arg _U_) {
+  node *nd = (node *)item;
+  if ((nd->key % 2) == 0) {
+    nd->key *= -1;
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+void test_nghttp2_pq_update(void) {
+  nghttp2_pq pq;
+  node nodes[10];
+  int i;
+  node *nd;
+  int ans[] = {-8, -6, -4, -2, 0, 1, 3, 5, 7, 9};
+
+  nghttp2_pq_init(&pq, node_compar, nghttp2_mem_default());
+
+  for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) {
+    nodes[i].key = i;
+    nodes[i].val = i;
+    nghttp2_pq_push(&pq, &nodes[i]);
+  }
+
+  nghttp2_pq_update(&pq, node_update, NULL);
+
+  for (i = 0; i < (int)(sizeof(nodes) / sizeof(nodes[0])); ++i) {
+    nd = nghttp2_pq_top(&pq);
+    CU_ASSERT(ans[i] == nd->key);
+    nghttp2_pq_pop(&pq);
+  }
+
+  nghttp2_pq_free(&pq);
+}
diff --git a/tests/nghttp2_pq_test.h b/tests/nghttp2_pq_test.h
new file mode 100644 (file)
index 0000000..7818194
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_PQ_TEST_H
+#define NGHTTP2_PQ_TEST_H
+
+void test_nghttp2_pq(void);
+void test_nghttp2_pq_update(void);
+
+#endif /* NGHTTP2_PQ_TEST_H */
diff --git a/tests/nghttp2_queue_test.c b/tests/nghttp2_queue_test.c
new file mode 100644 (file)
index 0000000..76cc98a
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_queue_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_queue.h"
+
+void test_nghttp2_queue(void) {
+  int ints[] = {1, 2, 3, 4, 5};
+  int i;
+  nghttp2_queue queue;
+  nghttp2_queue_init(&queue);
+  CU_ASSERT(nghttp2_queue_empty(&queue));
+  for (i = 0; i < 5; ++i) {
+    nghttp2_queue_push(&queue, &ints[i]);
+    CU_ASSERT_EQUAL(ints[0], *(int *)(nghttp2_queue_front(&queue)));
+    CU_ASSERT(!nghttp2_queue_empty(&queue));
+  }
+  for (i = 0; i < 5; ++i) {
+    CU_ASSERT_EQUAL(ints[i], *(int *)(nghttp2_queue_front(&queue)));
+    nghttp2_queue_pop(&queue);
+  }
+  CU_ASSERT(nghttp2_queue_empty(&queue));
+  nghttp2_queue_free(&queue);
+}
diff --git a/tests/nghttp2_queue_test.h b/tests/nghttp2_queue_test.h
new file mode 100644 (file)
index 0000000..944697e
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_QUEUE_TEST_H
+#define NGHTTP2_QUEUE_TEST_H
+
+void test_nghttp2_queue(void);
+
+#endif /* NGHTTP2_QUEUE_TEST_H */
diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c
new file mode 100644 (file)
index 0000000..a605169
--- /dev/null
@@ -0,0 +1,6655 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2013 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_session_test.h"
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_session.h"
+#include "nghttp2_stream.h"
+#include "nghttp2_net.h"
+#include "nghttp2_helper.h"
+#include "nghttp2_test_helper.h"
+#include "nghttp2_priority_spec.h"
+
+#define OB_CTRL(ITEM) nghttp2_outbound_item_get_ctrl_frame(ITEM)
+#define OB_CTRL_TYPE(ITEM) nghttp2_outbound_item_get_ctrl_frame_type(ITEM)
+#define OB_DATA(ITEM) nghttp2_outbound_item_get_data_frame(ITEM)
+
+typedef struct {
+  uint8_t buf[65535];
+  size_t length;
+} accumulator;
+
+typedef struct {
+  uint8_t data[8192];
+  uint8_t *datamark;
+  uint8_t *datalimit;
+  size_t feedseq[8192];
+  size_t seqidx;
+} scripted_data_feed;
+
+typedef struct {
+  accumulator *acc;
+  scripted_data_feed *df;
+  int frame_recv_cb_called, invalid_frame_recv_cb_called;
+  uint8_t recv_frame_type;
+  int frame_send_cb_called;
+  uint8_t sent_frame_type;
+  int frame_not_send_cb_called;
+  uint8_t not_sent_frame_type;
+  int not_sent_error;
+  int stream_close_cb_called;
+  size_t data_source_length;
+  int32_t stream_id;
+  size_t block_count;
+  int data_chunk_recv_cb_called;
+  const nghttp2_frame *frame;
+  size_t fixed_sendlen;
+  int header_cb_called;
+  int begin_headers_cb_called;
+  nghttp2_nv nv;
+  size_t data_chunk_len;
+  size_t padlen;
+  int begin_frame_cb_called;
+} my_user_data;
+
+static void scripted_data_feed_init2(scripted_data_feed *df,
+                                     nghttp2_bufs *bufs) {
+  nghttp2_buf_chain *ci;
+  nghttp2_buf *buf;
+  uint8_t *ptr;
+  size_t len;
+
+  memset(df, 0, sizeof(scripted_data_feed));
+  ptr = df->data;
+  len = 0;
+
+  for (ci = bufs->head; ci; ci = ci->next) {
+    buf = &ci->buf;
+    ptr = nghttp2_cpymem(ptr, buf->pos, nghttp2_buf_len(buf));
+    len += nghttp2_buf_len(buf);
+  }
+
+  df->datamark = df->data;
+  df->datalimit = df->data + len;
+  df->feedseq[0] = len;
+}
+
+static ssize_t null_send_callback(nghttp2_session *session _U_,
+                                  const uint8_t *data _U_, size_t len,
+                                  int flags _U_, void *user_data _U_) {
+  return len;
+}
+
+static ssize_t fail_send_callback(nghttp2_session *session _U_,
+                                  const uint8_t *data _U_, size_t len _U_,
+                                  int flags _U_, void *user_data _U_) {
+  return NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+static ssize_t fixed_bytes_send_callback(nghttp2_session *session _U_,
+                                         const uint8_t *data _U_, size_t len,
+                                         int flags _U_, void *user_data) {
+  size_t fixed_sendlen = ((my_user_data *)user_data)->fixed_sendlen;
+  return fixed_sendlen < len ? fixed_sendlen : len;
+}
+
+static ssize_t scripted_recv_callback(nghttp2_session *session _U_,
+                                      uint8_t *data, size_t len, int flags _U_,
+                                      void *user_data) {
+  scripted_data_feed *df = ((my_user_data *)user_data)->df;
+  size_t wlen = df->feedseq[df->seqidx] > len ? len : df->feedseq[df->seqidx];
+  memcpy(data, df->datamark, wlen);
+  df->datamark += wlen;
+  df->feedseq[df->seqidx] -= wlen;
+  if (df->feedseq[df->seqidx] == 0) {
+    ++df->seqidx;
+  }
+  return wlen;
+}
+
+static ssize_t eof_recv_callback(nghttp2_session *session _U_,
+                                 uint8_t *data _U_, size_t len _U_,
+                                 int flags _U_, void *user_data _U_) {
+  return NGHTTP2_ERR_EOF;
+}
+
+static ssize_t accumulator_send_callback(nghttp2_session *session _U_,
+                                         const uint8_t *buf, size_t len,
+                                         int flags _U_, void *user_data) {
+  accumulator *acc = ((my_user_data *)user_data)->acc;
+  assert(acc->length + len < sizeof(acc->buf));
+  memcpy(acc->buf + acc->length, buf, len);
+  acc->length += len;
+  return len;
+}
+
+static int on_begin_frame_callback(nghttp2_session *session _U_,
+                                   const nghttp2_frame_hd *hd _U_,
+                                   void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->begin_frame_cb_called;
+  return 0;
+}
+
+static int on_frame_recv_callback(nghttp2_session *session _U_,
+                                  const nghttp2_frame *frame, void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->frame_recv_cb_called;
+  ud->recv_frame_type = frame->hd.type;
+  return 0;
+}
+
+static int on_invalid_frame_recv_callback(nghttp2_session *session _U_,
+                                          const nghttp2_frame *frame _U_,
+                                          nghttp2_error_code error_code _U_,
+                                          void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->invalid_frame_recv_cb_called;
+  return 0;
+}
+
+static int on_frame_send_callback(nghttp2_session *session _U_,
+                                  const nghttp2_frame *frame, void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->frame_send_cb_called;
+  ud->sent_frame_type = frame->hd.type;
+  return 0;
+}
+
+static int on_frame_not_send_callback(nghttp2_session *session _U_,
+                                      const nghttp2_frame *frame, int lib_error,
+                                      void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->frame_not_send_cb_called;
+  ud->not_sent_frame_type = frame->hd.type;
+  ud->not_sent_error = lib_error;
+  return 0;
+}
+
+static int on_data_chunk_recv_callback(nghttp2_session *session _U_,
+                                       uint8_t flags _U_, int32_t stream_id _U_,
+                                       const uint8_t *data _U_, size_t len,
+                                       void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->data_chunk_recv_cb_called;
+  ud->data_chunk_len = len;
+  return 0;
+}
+
+static int pause_on_data_chunk_recv_callback(nghttp2_session *session _U_,
+                                             uint8_t flags _U_,
+                                             int32_t stream_id _U_,
+                                             const uint8_t *data _U_,
+                                             size_t len _U_, void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->data_chunk_recv_cb_called;
+  return NGHTTP2_ERR_PAUSE;
+}
+
+static ssize_t select_padding_callback(nghttp2_session *session _U_,
+                                       const nghttp2_frame *frame,
+                                       size_t max_payloadlen, void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  return nghttp2_min(max_payloadlen, frame->hd.length + ud->padlen);
+}
+
+static ssize_t too_large_data_source_length_callback(
+    nghttp2_session *session _U_, uint8_t frame_type _U_, int32_t stream_id _U_,
+    int32_t session_remote_window_size _U_,
+    int32_t stream_remote_window_size _U_, uint32_t remote_max_frame_size _U_,
+    void *user_data _U_) {
+  return NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
+}
+
+static ssize_t smallest_length_data_source_length_callback(
+    nghttp2_session *session _U_, uint8_t frame_type _U_, int32_t stream_id _U_,
+    int32_t session_remote_window_size _U_,
+    int32_t stream_remote_window_size _U_, uint32_t remote_max_frame_size _U_,
+    void *user_data _U_) {
+  return 1;
+}
+
+static ssize_t fixed_length_data_source_read_callback(
+    nghttp2_session *session _U_, int32_t stream_id _U_, uint8_t *buf _U_,
+    size_t len, uint32_t *data_flags, nghttp2_data_source *source _U_,
+    void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  size_t wlen;
+  if (len < ud->data_source_length) {
+    wlen = len;
+  } else {
+    wlen = ud->data_source_length;
+  }
+  ud->data_source_length -= wlen;
+  if (ud->data_source_length == 0) {
+    *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  }
+  return wlen;
+}
+
+static ssize_t temporal_failure_data_source_read_callback(
+    nghttp2_session *session _U_, int32_t stream_id _U_, uint8_t *buf _U_,
+    size_t len _U_, uint32_t *data_flags _U_, nghttp2_data_source *source _U_,
+    void *user_data _U_) {
+  return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+
+static ssize_t fail_data_source_read_callback(nghttp2_session *session _U_,
+                                              int32_t stream_id _U_,
+                                              uint8_t *buf _U_, size_t len _U_,
+                                              uint32_t *data_flags _U_,
+                                              nghttp2_data_source *source _U_,
+                                              void *user_data _U_) {
+  return NGHTTP2_ERR_CALLBACK_FAILURE;
+}
+
+/* static void no_stream_user_data_stream_close_callback */
+/* (nghttp2_session *session, */
+/*  int32_t stream_id, */
+/*  nghttp2_error_code error_code, */
+/*  void *user_data) */
+/* { */
+/*   my_user_data* my_data = (my_user_data*)user_data; */
+/*   ++my_data->stream_close_cb_called; */
+/* } */
+
+static ssize_t block_count_send_callback(nghttp2_session *session _U_,
+                                         const uint8_t *data _U_, size_t len,
+                                         int flags _U_, void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ssize_t r;
+  if (ud->block_count == 0) {
+    r = NGHTTP2_ERR_WOULDBLOCK;
+  } else {
+    --ud->block_count;
+    r = len;
+  }
+  return r;
+}
+
+static int on_header_callback(nghttp2_session *session _U_,
+                              const nghttp2_frame *frame, const uint8_t *name,
+                              size_t namelen, const uint8_t *value,
+                              size_t valuelen, uint8_t flags _U_,
+                              void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->header_cb_called;
+  ud->nv.name = (uint8_t *)name;
+  ud->nv.namelen = namelen;
+  ud->nv.value = (uint8_t *)value;
+  ud->nv.valuelen = valuelen;
+
+  ud->frame = frame;
+  return 0;
+}
+
+static int pause_on_header_callback(nghttp2_session *session,
+                                    const nghttp2_frame *frame,
+                                    const uint8_t *name, size_t namelen,
+                                    const uint8_t *value, size_t valuelen,
+                                    uint8_t flags, void *user_data) {
+  on_header_callback(session, frame, name, namelen, value, valuelen, flags,
+                     user_data);
+  return NGHTTP2_ERR_PAUSE;
+}
+
+static int temporal_failure_on_header_callback(
+    nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name,
+    size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags,
+    void *user_data) {
+  on_header_callback(session, frame, name, namelen, value, valuelen, flags,
+                     user_data);
+  return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
+}
+
+static int on_begin_headers_callback(nghttp2_session *session _U_,
+                                     const nghttp2_frame *frame _U_,
+                                     void *user_data) {
+  my_user_data *ud = (my_user_data *)user_data;
+  ++ud->begin_headers_cb_called;
+  return 0;
+}
+
+static ssize_t defer_data_source_read_callback(nghttp2_session *session _U_,
+                                               int32_t stream_id _U_,
+                                               uint8_t *buf _U_, size_t len _U_,
+                                               uint32_t *data_flags _U_,
+                                               nghttp2_data_source *source _U_,
+                                               void *user_data _U_) {
+  return NGHTTP2_ERR_DEFERRED;
+}
+
+static int on_stream_close_callback(nghttp2_session *session _U_,
+                                    int32_t stream_id _U_,
+                                    nghttp2_error_code error_code _U_,
+                                    void *user_data) {
+  my_user_data *my_data = (my_user_data *)user_data;
+  ++my_data->stream_close_cb_called;
+
+  return 0;
+}
+
+static nghttp2_settings_entry *dup_iv(const nghttp2_settings_entry *iv,
+                                      size_t niv) {
+  return nghttp2_frame_iv_copy(iv, niv, nghttp2_mem_default());
+}
+
+static nghttp2_priority_spec pri_spec_default = {0, NGHTTP2_DEFAULT_WEIGHT, 0};
+
+void test_nghttp2_session_recv(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  scripted_data_feed df;
+  my_user_data user_data;
+  const nghttp2_nv nv[] = {MAKE_NV("url", "/")};
+  nghttp2_bufs bufs;
+  ssize_t framelen;
+  nghttp2_frame frame;
+  ssize_t i;
+  nghttp2_outbound_item *item;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+  nghttp2_hd_deflater deflater;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.recv_callback = scripted_recv_callback;
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_begin_frame_callback = on_begin_frame_callback;
+
+  user_data.df = &df;
+
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  nvlen = ARRLEN(nv);
+  nghttp2_nv_array_copy(&nva, nv, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+
+  scripted_data_feed_init2(&df, &bufs);
+
+  framelen = nghttp2_bufs_len(&bufs);
+
+  /* Send 1 byte per each read */
+  for (i = 0; i < framelen; ++i) {
+    df.feedseq[i] = 1;
+  }
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  user_data.frame_recv_cb_called = 0;
+  user_data.begin_frame_cb_called = 0;
+
+  while ((ssize_t)df.seqidx < framelen) {
+    CU_ASSERT(0 == nghttp2_session_recv(session));
+  }
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+  CU_ASSERT(1 == user_data.begin_frame_cb_called);
+
+  nghttp2_bufs_reset(&bufs);
+
+  /* Received HEADERS without header block, which is valid */
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 5,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  scripted_data_feed_init2(&df, &bufs);
+  user_data.frame_recv_cb_called = 0;
+  user_data.begin_frame_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_recv(session));
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+  CU_ASSERT(1 == user_data.begin_frame_cb_called);
+
+  nghttp2_bufs_reset(&bufs);
+
+  /* Receive PRIORITY */
+  nghttp2_frame_priority_init(&frame.priority, 5, &pri_spec_default);
+
+  rv = nghttp2_frame_pack_priority(&bufs, &frame.priority);
+
+  CU_ASSERT(0 == rv);
+
+  nghttp2_frame_priority_free(&frame.priority);
+
+  scripted_data_feed_init2(&df, &bufs);
+
+  user_data.frame_recv_cb_called = 0;
+  user_data.begin_frame_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_recv(session));
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+  CU_ASSERT(1 == user_data.begin_frame_cb_called);
+
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+
+  /* Some tests for frame too large */
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+  /* Receive PING with too large payload */
+  nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL);
+
+  rv = nghttp2_frame_pack_ping(&bufs, &frame.ping);
+
+  CU_ASSERT(0 == rv);
+
+  /* Add extra 16 bytes */
+  nghttp2_bufs_seek_last_present(&bufs);
+  assert(nghttp2_buf_len(&bufs.cur->buf) >= 16);
+
+  bufs.cur->buf.last += 16;
+  nghttp2_put_uint32be(
+      bufs.cur->buf.pos,
+      (uint32_t)(((frame.hd.length + 16) << 8) + bufs.cur->buf.pos[3]));
+
+  nghttp2_frame_ping_free(&frame.ping);
+
+  scripted_data_feed_init2(&df, &bufs);
+  user_data.frame_recv_cb_called = 0;
+  user_data.begin_frame_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_recv(session));
+  CU_ASSERT(0 == user_data.frame_recv_cb_called);
+  CU_ASSERT(0 == user_data.begin_frame_cb_called);
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == item->frame.goaway.error_code);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_invalid_stream_id(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  scripted_data_feed df;
+  my_user_data user_data;
+  nghttp2_bufs bufs;
+  nghttp2_frame frame;
+  nghttp2_hd_deflater deflater;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.recv_callback = scripted_recv_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  user_data.df = &df;
+  user_data.invalid_frame_recv_cb_called = 0;
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  scripted_data_feed_init2(&df, &bufs);
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  CU_ASSERT(0 == nghttp2_session_recv(session));
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_invalid_frame(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  scripted_data_feed df;
+  my_user_data user_data;
+  const nghttp2_nv nv[] = {MAKE_NV("url", "/")};
+  nghttp2_bufs bufs;
+  nghttp2_frame frame;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+  nghttp2_hd_deflater deflater;
+  int rv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.recv_callback = scripted_recv_callback;
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  user_data.df = &df;
+  user_data.frame_send_cb_called = 0;
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  nghttp2_hd_deflate_init(&deflater, mem);
+  nvlen = ARRLEN(nv);
+  nghttp2_nv_array_copy(&nva, nv, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  scripted_data_feed_init2(&df, &bufs);
+
+  CU_ASSERT(0 == nghttp2_session_recv(session));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == user_data.frame_send_cb_called);
+
+  /* Receive exactly same bytes of HEADERS is treated as subsequent
+     HEADERS (e.g., trailers */
+  scripted_data_feed_init2(&df, &bufs);
+
+  CU_ASSERT(0 == nghttp2_session_recv(session));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == user_data.frame_send_cb_called);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_eof(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.recv_callback = eof_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+  CU_ASSERT(NGHTTP2_ERR_EOF == nghttp2_session_recv(session));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  uint8_t data[8092];
+  ssize_t rv;
+  nghttp2_outbound_item *item;
+  nghttp2_stream *stream;
+  nghttp2_frame_hd hd;
+  int i;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+
+  /* Create DATA frame with length 4KiB */
+  memset(data, 0, sizeof(data));
+  hd.length = 4096;
+  hd.type = NGHTTP2_DATA;
+  hd.flags = NGHTTP2_FLAG_NONE;
+  hd.stream_id = 1;
+  nghttp2_frame_pack_frame_hd(data, &hd);
+
+  /* stream 1 is not opened, so it must be responded with connection
+     error.  This is not mandated by the spec */
+  ud.data_chunk_recv_cb_called = 0;
+  ud.frame_recv_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+
+  CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+
+  /* Create stream 1 with CLOSING state. DATA is ignored. */
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_CLOSING, NULL);
+  /* Set initial window size 16383 to check stream flow control,
+     isolating it from the conneciton flow control */
+  stream->local_window_size = 16383;
+
+  ud.data_chunk_recv_cb_called = 0;
+  ud.frame_recv_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+
+  CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NULL == item);
+
+  /* This is normal case. DATA is acceptable. */
+  stream->state = NGHTTP2_STREAM_OPENED;
+
+  ud.data_chunk_recv_cb_called = 0;
+  ud.frame_recv_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+
+  CU_ASSERT(1 == ud.data_chunk_recv_cb_called);
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  ud.data_chunk_recv_cb_called = 0;
+  ud.frame_recv_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+
+  /* Now we got data more than initial-window-size / 2, WINDOW_UPDATE
+     must be queued */
+  CU_ASSERT(1 == ud.data_chunk_recv_cb_called);
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(1 == item->frame.window_update.hd.stream_id);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Set initial window size to 1MiB, so that we can check connection
+     flow control individually */
+  stream->local_window_size = 1 << 20;
+  /* Connection flow control takes into account DATA which is received
+     in the error condition. We have received 4096 * 4 bytes of
+     DATA. Additional 4 DATA frames, connection flow control will kick
+     in. */
+  for (i = 0; i < 5; ++i) {
+    rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+    CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+  }
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(0 == item->frame.window_update.hd.stream_id);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Reception of DATA with stream ID = 0 causes connection error */
+  hd.length = 4096;
+  hd.type = NGHTTP2_DATA;
+  hd.flags = NGHTTP2_FLAG_NONE;
+  hd.stream_id = 0;
+  nghttp2_frame_pack_frame_hd(data, &hd);
+
+  ud.data_chunk_recv_cb_called = 0;
+  ud.frame_recv_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + 4096);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 4096 == rv);
+
+  CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_continuation(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv1[] = {MAKE_NV("method", "GET"), MAKE_NV("path", "/")};
+  nghttp2_nv *nva;
+  size_t nvlen;
+  nghttp2_frame frame;
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  ssize_t rv;
+  my_user_data ud;
+  nghttp2_hd_deflater deflater;
+  uint8_t data[1024];
+  size_t datalen;
+  nghttp2_frame_hd cont_hd;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_header_callback = on_header_callback;
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+  callbacks.on_begin_frame_callback = on_begin_frame_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  /* Make 1 HEADERS and insert CONTINUATION header */
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  /* make sure that all data is in the first buf */
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  /* HEADERS's payload is 1 byte */
+  memcpy(data, buf->pos, NGHTTP2_FRAME_HDLEN + 1);
+  datalen = NGHTTP2_FRAME_HDLEN + 1;
+  buf->pos += NGHTTP2_FRAME_HDLEN + 1;
+
+  nghttp2_put_uint32be(data, (1 << 8) + data[3]);
+
+  /* First CONTINUATION, 2 bytes */
+  nghttp2_frame_hd_init(&cont_hd, 2, NGHTTP2_CONTINUATION, NGHTTP2_FLAG_NONE,
+                        1);
+
+  nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd);
+  datalen += NGHTTP2_FRAME_HDLEN;
+
+  memcpy(data + datalen, buf->pos, cont_hd.length);
+  datalen += cont_hd.length;
+  buf->pos += cont_hd.length;
+
+  /* Second CONTINUATION, rest of the bytes */
+  nghttp2_frame_hd_init(&cont_hd, nghttp2_buf_len(buf), NGHTTP2_CONTINUATION,
+                        NGHTTP2_FLAG_END_HEADERS, 1);
+
+  nghttp2_frame_pack_frame_hd(data + datalen, &cont_hd);
+  datalen += NGHTTP2_FRAME_HDLEN;
+
+  memcpy(data + datalen, buf->pos, cont_hd.length);
+  datalen += cont_hd.length;
+  buf->pos += cont_hd.length;
+
+  CU_ASSERT(0 == nghttp2_buf_len(buf));
+
+  ud.header_cb_called = 0;
+  ud.begin_frame_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, data, datalen);
+  CU_ASSERT((ssize_t)datalen == rv);
+  CU_ASSERT(2 == ud.header_cb_called);
+  CU_ASSERT(3 == ud.begin_frame_cb_called);
+
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+
+  /* Expecting CONTINUATION, but get the other frame */
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  /* HEADERS without END_HEADERS flag */
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_NONE, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  nghttp2_bufs_reset(&bufs);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  /* make sure that all data is in the first buf */
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  memcpy(data, buf->pos, nghttp2_buf_len(buf));
+  datalen = nghttp2_buf_len(buf);
+
+  /* Followed by PRIORITY */
+  nghttp2_priority_spec_default_init(&pri_spec);
+
+  nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+  nghttp2_bufs_reset(&bufs);
+
+  rv = nghttp2_frame_pack_priority(&bufs, &frame.priority);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  memcpy(data + datalen, buf->pos, nghttp2_buf_len(buf));
+  datalen += nghttp2_buf_len(buf);
+
+  ud.begin_headers_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, data, datalen);
+  CU_ASSERT((ssize_t)datalen == rv);
+
+  CU_ASSERT(1 == ud.begin_headers_cb_called);
+  CU_ASSERT(NGHTTP2_GOAWAY ==
+            nghttp2_session_get_next_ob_item(session)->frame.hd.type);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_headers_with_priority(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv1[] = {MAKE_NV("method", "GET"), MAKE_NV("path", "/")};
+  nghttp2_nv *nva;
+  size_t nvlen;
+  nghttp2_frame frame;
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  ssize_t rv;
+  my_user_data ud;
+  nghttp2_hd_deflater deflater;
+  nghttp2_outbound_item *item;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_stream *stream;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  open_stream(session, 1);
+
+  /* With NGHTTP2_FLAG_PRIORITY without exclusive flag set */
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+
+  nghttp2_priority_spec_init(&pri_spec, 1, 99, 0);
+
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             3, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
+
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(nghttp2_buf_len(buf) == rv);
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+  stream = nghttp2_session_get_stream(session, 3);
+
+  CU_ASSERT(99 == stream->weight);
+  CU_ASSERT(1 == stream->dep_prev->stream_id);
+
+  nghttp2_bufs_reset(&bufs);
+
+  /* With NGHTTP2_FLAG_PRIORITY, but cut last 1 byte to make it
+     invalid. */
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 99, 0);
+
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             5, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
+
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 5 + 2 == nghttp2_bufs_len(&bufs));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  buf = &bufs.head->buf;
+  /* Make payload shorter than required length to store priroty
+     group */
+  nghttp2_put_uint32be(buf->pos, (4 << 8) + buf->pos[3]);
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(nghttp2_buf_len(buf) == rv);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+  stream = nghttp2_session_get_stream(session, 5);
+
+  CU_ASSERT(NULL == stream);
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NULL != item);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_FRAME_SIZE_ERROR == item->frame.goaway.error_code);
+
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+
+  /* Check dep_stream_id == stream_id */
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+
+  nghttp2_priority_spec_init(&pri_spec, 1, 0, 0);
+
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
+
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(nghttp2_buf_len(buf) == rv);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+  stream = nghttp2_session_get_stream(session, 1);
+
+  CU_ASSERT(NULL == stream);
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NULL != item);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
+
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_premature_headers(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv1[] = {MAKE_NV("method", "GET"), MAKE_NV("path", "/")};
+  nghttp2_nv *nva;
+  size_t nvlen;
+  nghttp2_frame frame;
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  ssize_t rv;
+  my_user_data ud;
+  nghttp2_hd_deflater deflater;
+  nghttp2_outbound_item *item;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  /* Intentionally feed payload cutting last 1 byte off */
+  nghttp2_put_uint32be(buf->pos,
+                       (uint32_t)(((frame.hd.length - 1) << 8) + buf->pos[3]));
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf) - 1);
+
+  CU_ASSERT((ssize_t)(nghttp2_buf_len(buf) - 1) == rv);
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NULL != item);
+  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_COMPRESSION_ERROR == item->frame.rst_stream.error_code);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_unknown_frame(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  uint8_t data[16384];
+  size_t datalen;
+  nghttp2_frame_hd hd;
+  ssize_t rv;
+
+  nghttp2_frame_hd_init(&hd, 16000, 99, NGHTTP2_FLAG_NONE, 0);
+
+  nghttp2_frame_pack_frame_hd(data, &hd);
+  datalen = NGHTTP2_FRAME_HDLEN + hd.length;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  ud.frame_recv_cb_called = 0;
+
+  /* Unknown frame must be ignored */
+  rv = nghttp2_session_mem_recv(session, data, datalen);
+
+  CU_ASSERT(rv == (ssize_t)datalen);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_unexpected_continuation(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  uint8_t data[16384];
+  size_t datalen;
+  nghttp2_frame_hd hd;
+  ssize_t rv;
+  nghttp2_outbound_item *item;
+
+  nghttp2_frame_hd_init(&hd, 16000, NGHTTP2_CONTINUATION,
+                        NGHTTP2_FLAG_END_HEADERS, 1);
+
+  nghttp2_frame_pack_frame_hd(data, &hd);
+  datalen = NGHTTP2_FRAME_HDLEN + hd.length;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  open_stream(session, 1);
+
+  ud.frame_recv_cb_called = 0;
+
+  /* unexpected CONTINUATION must be treated as connection error */
+  rv = nghttp2_session_mem_recv(session, data, datalen);
+
+  CU_ASSERT(rv == (ssize_t)datalen);
+  CU_ASSERT(0 == ud.frame_recv_cb_called);
+
+  item = nghttp2_session_get_next_ob_item(session);
+
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_settings_header_table_size(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_frame frame;
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  ssize_t rv;
+  my_user_data ud;
+  nghttp2_settings_entry iv[3];
+  nghttp2_nv nv = MAKE_NV(":authority", "example.org");
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 3000;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 16384;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2),
+                              2);
+
+  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(rv == nghttp2_buf_len(buf));
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+  CU_ASSERT(3000 == session->remote_settings.header_table_size);
+  CU_ASSERT(16384 == session->remote_settings.initial_window_size);
+
+  nghttp2_bufs_reset(&bufs);
+
+  /* 2 SETTINGS_HEADER_TABLE_SIZE */
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 3001;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 16383;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[2].value = 3001;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3),
+                              3);
+
+  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(rv == nghttp2_buf_len(buf));
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+  CU_ASSERT(3001 == session->remote_settings.header_table_size);
+  CU_ASSERT(16383 == session->remote_settings.initial_window_size);
+
+  nghttp2_bufs_reset(&bufs);
+
+  /* 2 SETTINGS_HEADER_TABLE_SIZE; first entry clears dynamic header
+     table. */
+
+  nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL);
+  nghttp2_session_send(session);
+
+  CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len);
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 0;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 16382;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[2].value = 4096;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3),
+                              3);
+
+  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(rv == nghttp2_buf_len(buf));
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+  CU_ASSERT(4096 == session->remote_settings.header_table_size);
+  CU_ASSERT(16382 == session->remote_settings.initial_window_size);
+  CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len);
+
+  nghttp2_bufs_reset(&bufs);
+
+  /* 2 SETTINGS_HEADER_TABLE_SIZE; second entry clears dynamic header
+     table. */
+
+  nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL);
+  nghttp2_session_send(session);
+
+  CU_ASSERT(0 < session->hd_deflater.ctx.hd_table.len);
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 3000;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 16381;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[2].value = 0;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 3),
+                              3);
+
+  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  ud.frame_recv_cb_called = 0;
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
+
+  CU_ASSERT(rv == nghttp2_buf_len(buf));
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+
+  CU_ASSERT(0 == session->remote_settings.header_table_size);
+  CU_ASSERT(16381 == session->remote_settings.initial_window_size);
+  CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len);
+
+  nghttp2_bufs_reset(&bufs);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_too_large_frame_length(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  uint8_t buf[NGHTTP2_FRAME_HDLEN];
+  nghttp2_outbound_item *item;
+  nghttp2_frame_hd hd;
+
+  /* Initial max frame size is NGHTTP2_MAX_FRAME_SIZE_MIN */
+  nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_FRAME_SIZE_MIN + 1, NGHTTP2_HEADERS,
+                        NGHTTP2_FLAG_NONE, 1);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  nghttp2_frame_pack_frame_hd(buf, &hd);
+
+  CU_ASSERT(sizeof(buf) == nghttp2_session_mem_recv(session, buf, sizeof(buf)));
+
+  item = nghttp2_session_get_next_ob_item(session);
+
+  CU_ASSERT(item != NULL);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_continue(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  const nghttp2_nv nv1[] = {MAKE_NV(":method", "GET"), MAKE_NV(":path", "/")};
+  const nghttp2_nv nv2[] = {MAKE_NV("user-agent", "nghttp2/1.0.0"),
+                            MAKE_NV("alpha", "bravo")};
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  size_t framelen1, framelen2;
+  ssize_t rv;
+  uint8_t buffer[4096];
+  nghttp2_buf databuf;
+  nghttp2_frame frame;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+  const nghttp2_frame *recv_frame;
+  nghttp2_frame_hd data_hd;
+  nghttp2_hd_deflater deflater;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+  nghttp2_buf_wrap_init(&databuf, buffer, sizeof(buffer));
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_data_chunk_recv_callback = pause_on_data_chunk_recv_callback;
+  callbacks.on_header_callback = pause_on_header_callback;
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  /* Make 2 HEADERS frames */
+  nvlen = ARRLEN(nv1);
+  nghttp2_nv_array_copy(&nva, nv1, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  buf = &bufs.head->buf;
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  framelen1 = nghttp2_buf_len(buf);
+  databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf));
+
+  nvlen = ARRLEN(nv2);
+  nghttp2_nv_array_copy(&nva, nv2, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3,
+                             NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  nghttp2_bufs_reset(&bufs);
+  rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
+
+  framelen2 = nghttp2_buf_len(buf);
+  databuf.last = nghttp2_cpymem(databuf.last, buf->pos, nghttp2_buf_len(buf));
+
+  /* Receive 1st HEADERS and pause */
+  user_data.begin_headers_cb_called = 0;
+  user_data.header_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+
+  CU_ASSERT(rv >= 0);
+  databuf.pos += rv;
+
+  recv_frame = user_data.frame;
+  CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type);
+  CU_ASSERT(framelen1 - NGHTTP2_FRAME_HDLEN == recv_frame->hd.length);
+
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.header_cb_called);
+
+  CU_ASSERT(nghttp2_nv_equal(&nv1[0], &user_data.nv));
+
+  /* get 2nd header field */
+  user_data.begin_headers_cb_called = 0;
+  user_data.header_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+
+  CU_ASSERT(rv >= 0);
+  databuf.pos += rv;
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.header_cb_called);
+
+  CU_ASSERT(nghttp2_nv_equal(&nv1[1], &user_data.nv));
+
+  /* will call end_headers_callback and receive 2nd HEADERS and pause */
+  user_data.begin_headers_cb_called = 0;
+  user_data.header_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+
+  CU_ASSERT(rv >= 0);
+  databuf.pos += rv;
+
+  recv_frame = user_data.frame;
+  CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type);
+  CU_ASSERT(framelen2 - NGHTTP2_FRAME_HDLEN == recv_frame->hd.length);
+
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.header_cb_called);
+
+  CU_ASSERT(nghttp2_nv_equal(&nv2[0], &user_data.nv));
+
+  /* get 2nd header field */
+  user_data.begin_headers_cb_called = 0;
+  user_data.header_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+
+  CU_ASSERT(rv >= 0);
+  databuf.pos += rv;
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.header_cb_called);
+
+  CU_ASSERT(nghttp2_nv_equal(&nv2[1], &user_data.nv));
+
+  /* No input data, frame_recv_callback is called */
+  user_data.begin_headers_cb_called = 0;
+  user_data.header_cb_called = 0;
+  user_data.frame_recv_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+
+  CU_ASSERT(rv >= 0);
+  databuf.pos += rv;
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(0 == user_data.header_cb_called);
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+
+  /* Receive DATA */
+  nghttp2_frame_hd_init(&data_hd, 16, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 1);
+
+  nghttp2_buf_reset(&databuf);
+  nghttp2_frame_pack_frame_hd(databuf.pos, &data_hd);
+
+  /* Intentionally specify larger buffer size to see pause is kicked
+     in. */
+  databuf.last = databuf.end;
+
+  user_data.frame_recv_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+
+  CU_ASSERT(16 + NGHTTP2_FRAME_HDLEN == rv);
+  CU_ASSERT(0 == user_data.frame_recv_cb_called);
+
+  /* Next nghttp2_session_mem_recv invokes on_frame_recv_callback and
+     pause again in on_data_chunk_recv_callback since we pass same
+     DATA frame. */
+  user_data.frame_recv_cb_called = 0;
+  rv =
+      nghttp2_session_mem_recv(session, databuf.pos, nghttp2_buf_len(&databuf));
+  CU_ASSERT(16 + NGHTTP2_FRAME_HDLEN == rv);
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+
+  /* And finally call on_frame_recv_callback with 0 size input */
+  user_data.frame_recv_cb_called = 0;
+  rv = nghttp2_session_mem_recv(session, NULL, 0);
+  CU_ASSERT(0 == rv);
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_add_frame(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  accumulator acc;
+  my_user_data user_data;
+  const nghttp2_nv nv[] = {MAKE_NV("method", "GET"), MAKE_NV("scheme", "https"),
+                           MAKE_NV("url", "/"), MAKE_NV("version", "HTTP/1.1")};
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = accumulator_send_callback;
+
+  acc.length = 0;
+  user_data.acc = &acc;
+
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &user_data));
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nvlen = ARRLEN(nv);
+  nghttp2_nv_array_copy(&nva, nv, nvlen, mem);
+
+  nghttp2_frame_headers_init(
+      &frame->headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+      session->next_stream_id, NGHTTP2_HCAT_REQUEST, NULL, nva, nvlen);
+
+  session->next_stream_id += 2;
+
+  CU_ASSERT(0 == nghttp2_session_add_item(session, item));
+  CU_ASSERT(0 == nghttp2_pq_empty(&session->ob_ss_pq));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_HEADERS == acc.buf[3]);
+  CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY) == acc.buf[4]);
+  /* check stream id */
+  CU_ASSERT(1 == nghttp2_get_uint32(&acc.buf[5]));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_request_headers_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  int32_t stream_id = 1;
+  nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")};
+  nghttp2_nv *nva;
+  size_t nvlen;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 255, 0);
+
+  nghttp2_frame_headers_init(
+      &frame.headers, NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+      stream_id, NGHTTP2_HCAT_REQUEST, &pri_spec, NULL, 0);
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  stream = nghttp2_session_get_stream(session, stream_id);
+  CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+  CU_ASSERT(255 == stream->weight);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  /* More than un-ACKed max concurrent streams leads REFUSED_STREAM */
+  session->pending_local_max_concurrent_stream = 1;
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             3, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+  session->local_settings.max_concurrent_streams =
+      NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+
+  /* Stream ID less than or equal to the previouly received request
+     HEADERS is just ignored due to race condition */
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             3, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  /* Stream ID is our side and it is idle stream ID, then treat it as
+     connection error */
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             2, NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+
+  /* Check malformed headers. The library accept it. */
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+  nvlen = ARRLEN(malformed_nva);
+  nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem);
+  nghttp2_frame_headers_init(&frame.headers,
+                             NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
+                             1, NGHTTP2_HCAT_HEADERS, NULL, nva, nvlen);
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+
+  /* Check client side */
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  /* Receiving peer's idle stream ID is subject to connection error */
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
+                             NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
+
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  /* Receiving our's idle stream ID is subject to connection error */
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
+
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  session->next_stream_id = 5;
+
+  /* Stream ID which is not idle and not in stream map is just
+     ignored */
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3,
+                             NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
+
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+  /* Stream ID which is equal to local_last_stream_id is ok. */
+  session->local_last_stream_id = 3;
+
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3,
+                             NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
+
+  CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session, &frame));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  /* If GOAWAY has been sent, new stream is ignored */
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 5,
+                             NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
+
+  session->goaway_flags |= NGHTTP2_GOAWAY_SENT;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(0 == (session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_response_headers_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_on_response_headers_received(session, &frame,
+                                                              stream));
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_headers_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+  nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 1,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream));
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+
+  /* stream closed */
+  frame.hd.flags |= NGHTTP2_FLAG_END_STREAM;
+
+  CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream));
+  CU_ASSERT(2 == user_data.begin_headers_cb_called);
+
+  /* Check to see when NGHTTP2_STREAM_CLOSING, incoming HEADERS is
+     discarded. */
+  stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_CLOSING, NULL);
+  frame.hd.stream_id = 3;
+  frame.hd.flags = NGHTTP2_FLAG_END_HEADERS;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_headers_received(session, &frame, stream));
+  /* See no counters are updated */
+  CU_ASSERT(2 == user_data.begin_headers_cb_called);
+  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);
+
+  /* Server initiated stream */
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  /* half closed (remote) */
+  frame.hd.flags = NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM;
+  frame.hd.stream_id = 2;
+
+  CU_ASSERT(0 == nghttp2_session_on_headers_received(session, &frame, stream));
+  CU_ASSERT(3 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+
+  nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+
+  /* Further reception of HEADERS is subject to stream error */
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_headers_received(session, &frame, stream));
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_push_response_headers_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  nghttp2_outbound_item *item;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 2,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  /* nghttp2_session_on_push_response_headers_received assumes
+     stream's state is NGHTTP2_STREAM_RESERVED and session->server is
+     0. */
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_on_push_response_headers_received(
+                     session, &frame, stream));
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+  CU_ASSERT(1 == session->num_incoming_streams);
+
+  /* If un-ACKed max concurrent streams limit is exceeded,
+     RST_STREAMed */
+  session->pending_local_max_concurrent_stream = 1;
+  stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  frame.hd.stream_id = 4;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_response_headers_received(session, &frame,
+                                                              stream));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
+  CU_ASSERT(1 == session->num_incoming_streams);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == session->num_incoming_streams);
+
+  /* If ACKed max concurrent streams limit is exceeded, GOAWAY is
+     issued */
+  session->local_settings.max_concurrent_streams = 1;
+
+  stream = nghttp2_session_open_stream(session, 6, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  frame.hd.stream_id = 6;
+
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_response_headers_received(session, &frame,
+                                                              stream));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
+  CU_ASSERT(1 == session->num_incoming_streams);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_priority_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream, *dep_stream;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_outbound_item *item;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 2, 0);
+
+  nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+
+  /* depend on stream 0 */
+  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+  CU_ASSERT(2 == stream->weight);
+
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  dep_stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE,
+                                           &pri_spec_default,
+                                           NGHTTP2_STREAM_OPENING, NULL);
+
+  frame.hd.stream_id = 2;
+
+  /* using dependency stream */
+  nghttp2_priority_spec_init(&frame.priority.pri_spec, 3, 1, 0);
+
+  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+  CU_ASSERT(dep_stream == stream->dep_prev);
+
+  /* PRIORITY against idle stream */
+
+  frame.hd.stream_id = 100;
+
+  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+  stream = nghttp2_session_get_stream_raw(session, frame.hd.stream_id);
+
+  CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
+  CU_ASSERT(dep_stream == stream->dep_prev);
+
+  nghttp2_frame_priority_free(&frame.priority);
+  nghttp2_session_del(session);
+
+  /* Check dep_stream_id == stream_id case */
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENED, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 1, 0, 0);
+
+  nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+
+  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+  item = nghttp2_session_get_next_ob_item(session);
+
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  nghttp2_frame_priority_free(&frame.priority);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_rst_stream_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_PROTOCOL_ERROR);
+
+  CU_ASSERT(0 == nghttp2_session_on_rst_stream_received(session, &frame));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1));
+
+  nghttp2_frame_rst_stream_free(&frame.rst_stream);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_settings_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_stream *stream1, *stream2;
+  nghttp2_frame frame;
+  const size_t niv = 5;
+  nghttp2_settings_entry iv[255];
+  nghttp2_outbound_item *item;
+  nghttp2_nv nv = MAKE_NV(":authority", "example.org");
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = 50;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[1].value = 1000000009;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[2].value = 64 * 1024;
+
+  iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[3].value = 1024;
+
+  iv[4].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  iv[4].value = 0;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  session->remote_settings.initial_window_size = 16 * 1024;
+
+  stream1 = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                        &pri_spec_default,
+                                        NGHTTP2_STREAM_OPENING, NULL);
+  stream2 = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                        &pri_spec_default,
+                                        NGHTTP2_STREAM_OPENING, NULL);
+  /* Set window size for each streams and will see how settings
+     updates these values */
+  stream1->remote_window_size = 16 * 1024;
+  stream2->remote_window_size = -48 * 1024;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
+                              dup_iv(iv, niv), niv);
+
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+  CU_ASSERT(1000000009 == session->remote_settings.max_concurrent_streams);
+  CU_ASSERT(64 * 1024 == session->remote_settings.initial_window_size);
+  CU_ASSERT(1024 == session->remote_settings.header_table_size);
+  CU_ASSERT(0 == session->remote_settings.enable_push);
+
+  CU_ASSERT(64 * 1024 == stream1->remote_window_size);
+  CU_ASSERT(0 == stream2->remote_window_size);
+
+  frame.settings.iv[2].value = 16 * 1024;
+
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+
+  CU_ASSERT(16 * 1024 == stream1->remote_window_size);
+  CU_ASSERT(-48 * 1024 == stream2->remote_window_size);
+
+  CU_ASSERT(16 * 1024 == nghttp2_session_get_stream_remote_window_size(
+                             session, stream1->stream_id));
+  CU_ASSERT(0 == nghttp2_session_get_stream_remote_window_size(
+                     session, stream2->stream_id));
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+
+  nghttp2_session_del(session);
+
+  /* Check ACK with niv > 0 */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, dup_iv(iv, 1),
+                              1);
+  /* Specify inflight_iv deliberately */
+  session->inflight_iv = frame.settings.iv;
+  session->inflight_niv = frame.settings.niv;
+
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(item != NULL);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  session->inflight_iv = NULL;
+  session->inflight_niv = -1;
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+  nghttp2_session_del(session);
+
+  /* Check ACK against no inflight SETTINGS */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
+
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(item != NULL);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+  nghttp2_session_del(session);
+
+  /* Check that 2 SETTINGS_HEADER_TABLE_SIZE 0 and 4096 are included
+     and header table size is once cleared to 0. */
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+
+  nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL);
+
+  nghttp2_session_send(session);
+
+  CU_ASSERT(session->hd_deflater.ctx.hd_table.len > 0);
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 0;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[1].value = 2048;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2),
+                              2);
+
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+
+  CU_ASSERT(0 == session->hd_deflater.ctx.hd_table.len);
+  CU_ASSERT(2048 == session->hd_deflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(2048 == session->remote_settings.header_table_size);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+  nghttp2_session_del(session);
+
+  /* Check too large SETTINGS_MAX_FRAME_SIZE */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE;
+  iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1;
+
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1),
+                              1);
+
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
+
+  item = nghttp2_session_get_next_ob_item(session);
+
+  CU_ASSERT(item != NULL);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+
+  nghttp2_frame_settings_free(&frame.settings, mem);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_push_promise_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream, *promised_stream;
+  nghttp2_outbound_item *item;
+  nghttp2_nv malformed_nva[] = {MAKE_NV(":path", "\x01")};
+  nghttp2_nv *nva;
+  size_t nvlen;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_begin_headers_callback = on_begin_headers_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+                                  1, 2, NULL, 0);
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  promised_stream = nghttp2_session_get_stream(session, 2);
+  CU_ASSERT(NGHTTP2_STREAM_RESERVED == promised_stream->state);
+  CU_ASSERT(2 == session->last_recv_stream_id);
+
+  /* Attempt to PUSH_PROMISE against half close (remote) */
+  nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD);
+  frame.push_promise.promised_stream_id = 4;
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 4));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+  CU_ASSERT(4 == item->frame.hd.stream_id);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.rst_stream.error_code);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(4 == session->last_recv_stream_id);
+
+  /* Attempt to PUSH_PROMISE against stream in closing state */
+  stream->shut_flags = NGHTTP2_SHUT_NONE;
+  stream->state = NGHTTP2_STREAM_CLOSING;
+  frame.push_promise.promised_stream_id = 6;
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 6));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+  CU_ASSERT(6 == item->frame.hd.stream_id);
+  CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Attempt to PUSH_PROMISE against non-existent stream */
+  frame.hd.stream_id = 3;
+  frame.push_promise.promised_stream_id = 8;
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(0 == item->frame.hd.stream_id);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  /* Same ID twice */
+  stream->state = NGHTTP2_STREAM_OPENING;
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 8));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* After GOAWAY, PUSH_PROMISE will be discarded */
+  frame.push_promise.promised_stream_id = 10;
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 10));
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  /* Attempt to PUSH_PROMISE against reserved (remote) stream */
+  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+                                  2, 4, NULL, 0);
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+
+  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+  nghttp2_session_del(session);
+
+  /* Disable PUSH */
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  session->local_settings.enable_push = 0;
+
+  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+                                  1, 2, NULL, 0);
+
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(0 == user_data.begin_headers_cb_called);
+  CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called);
+
+  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+  nghttp2_session_del(session);
+
+  /* Check malformed headers. We accept malformed headers */
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+  nvlen = ARRLEN(malformed_nva);
+  nghttp2_nv_array_copy(&nva, malformed_nva, nvlen, mem);
+  nghttp2_frame_push_promise_init(&frame.push_promise, NGHTTP2_FLAG_END_HEADERS,
+                                  1, 2, nva, nvlen);
+  user_data.begin_headers_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_on_push_promise_received(session, &frame));
+
+  CU_ASSERT(1 == user_data.begin_headers_cb_called);
+  CU_ASSERT(0 == user_data.invalid_frame_recv_cb_called);
+
+  nghttp2_frame_push_promise_free(&frame.push_promise, mem);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_ping_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_outbound_item *top;
+  const uint8_t opaque_data[] = "01234567";
+
+  user_data.frame_recv_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_ACK, opaque_data);
+
+  CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame));
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+
+  /* Since this ping frame has PONG flag set, no further action is
+     performed. */
+  CU_ASSERT(NULL == nghttp2_session_get_ob_pq_top(session));
+
+  /* Clear the flag, and receive it again */
+  frame.hd.flags = NGHTTP2_FLAG_NONE;
+
+  CU_ASSERT(0 == nghttp2_session_on_ping_received(session, &frame));
+  CU_ASSERT(2 == user_data.frame_recv_cb_called);
+  top = nghttp2_session_get_ob_pq_top(session);
+  CU_ASSERT(NGHTTP2_PING == top->frame.hd.type);
+  CU_ASSERT(NGHTTP2_FLAG_ACK == top->frame.hd.flags);
+  CU_ASSERT(memcmp(opaque_data, top->frame.ping.opaque_data, 8) == 0);
+
+  nghttp2_frame_ping_free(&frame.ping);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_goaway_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  int i;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  user_data.frame_recv_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+  callbacks.on_stream_close_callback = on_stream_close_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  for (i = 1; i <= 7; ++i) {
+    open_stream(session, i);
+  }
+
+  nghttp2_frame_goaway_init(&frame.goaway, 3, NGHTTP2_PROTOCOL_ERROR, NULL, 0);
+
+  user_data.stream_close_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_on_goaway_received(session, &frame));
+
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+  CU_ASSERT(3 == session->remote_last_stream_id);
+  /* on_stream_close should be callsed for 2 times (stream 5 and 7) */
+  CU_ASSERT(2 == user_data.stream_close_cb_called);
+
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 1));
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 2));
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 3));
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 4));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 5));
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 6));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 7));
+
+  nghttp2_frame_goaway_free(&frame.goaway, mem);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_window_update_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  nghttp2_outbound_item *data_item;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_invalid_frame_recv_callback = on_invalid_frame_recv_callback;
+  user_data.frame_recv_cb_called = 0;
+  user_data.invalid_frame_recv_cb_called = 0;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+
+  data_item = create_data_ob_item();
+
+  CU_ASSERT(0 == nghttp2_stream_attach_item(stream, data_item, session));
+
+  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1,
+                                   16 * 1024);
+
+  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
+  CU_ASSERT(1 == user_data.frame_recv_cb_called);
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 ==
+            stream->remote_window_size);
+
+  CU_ASSERT(0 ==
+            nghttp2_stream_defer_item(
+                stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL, session));
+
+  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
+  CU_ASSERT(2 == user_data.frame_recv_cb_called);
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 16 * 1024 * 2 ==
+            stream->remote_window_size);
+  CU_ASSERT(0 == (stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL));
+
+  nghttp2_frame_window_update_free(&frame.window_update);
+
+  /* Receiving WINDOW_UPDATE on reserved (remote) stream is a
+     connection error */
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+
+  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2,
+                                   4096);
+
+  CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
+  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
+  CU_ASSERT(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND);
+
+  nghttp2_frame_window_update_free(&frame.window_update);
+
+  nghttp2_session_del(session);
+
+  /* Receiving WINDOW_UPDATE on reserved (local) stream is allowed */
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+
+  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 2,
+                                   4096);
+
+  CU_ASSERT(0 == nghttp2_session_on_window_update_received(session, &frame));
+  CU_ASSERT(!(session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND));
+
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 4096 == stream->remote_window_size);
+
+  nghttp2_frame_window_update_free(&frame.window_update);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_data_received(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_outbound_item *top;
+  nghttp2_stream *stream;
+  nghttp2_frame frame;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_frame_hd_init(&frame.hd, 4096, NGHTTP2_DATA, NGHTTP2_FLAG_NONE, 2);
+
+  CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame));
+  CU_ASSERT(0 == stream->shut_flags);
+
+  frame.hd.flags = NGHTTP2_FLAG_END_STREAM;
+
+  CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame));
+  CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags);
+
+  /* If NGHTTP2_STREAM_CLOSING state, DATA frame is discarded. */
+  stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_CLOSING, NULL);
+
+  frame.hd.flags = NGHTTP2_FLAG_NONE;
+  frame.hd.stream_id = 4;
+
+  CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame));
+  CU_ASSERT(NULL == nghttp2_session_get_ob_pq_top(session));
+
+  /* Check INVALID_STREAM case: DATA frame with stream ID which does
+     not exist. */
+
+  frame.hd.stream_id = 6;
+
+  CU_ASSERT(0 == nghttp2_session_on_data_received(session, &frame));
+  top = nghttp2_session_get_ob_pq_top(session);
+  /* DATA against nonexistent stream is just ignored for now */
+  CU_ASSERT(top == NULL);
+  /* CU_ASSERT(NGHTTP2_RST_STREAM == top->frame.hd.type); */
+  /* CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == top->frame.rst_stream.error_code); */
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_headers_start_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS,
+                             session->next_stream_id, NGHTTP2_HCAT_REQUEST,
+                             NULL, NULL, 0);
+  session->next_stream_id += 2;
+
+  nghttp2_session_add_item(session, item);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  stream = nghttp2_session_get_stream(session, 1);
+  CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_headers_reply(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
+  nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  nghttp2_session_add_item(session, item);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  stream = nghttp2_session_get_stream(session, 2);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_headers_frame_size_error(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_nv *nva;
+  ssize_t nvlen;
+  size_t vallen = NGHTTP2_HD_MAX_NV;
+  nghttp2_nv nv[28];
+  size_t nnv = ARRLEN(nv);
+  size_t i;
+  my_user_data ud;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  for (i = 0; i < nnv; ++i) {
+    nv[i].name = (uint8_t *)"header";
+    nv[i].namelen = strlen((const char *)nv[i].name);
+    nv[i].value = malloc(vallen + 1);
+    memset(nv[i].value, '0' + (int)i, vallen);
+    nv[i].value[vallen] = '\0';
+    nv[i].valuelen = vallen;
+    nv[i].flags = NGHTTP2_NV_FLAG_NONE;
+  }
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  nvlen = nnv;
+  nghttp2_nv_array_copy(&nva, nv, nvlen, mem);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS,
+                             session->next_stream_id, NGHTTP2_HCAT_REQUEST,
+                             NULL, nva, nvlen);
+
+  session->next_stream_id += 2;
+
+  nghttp2_session_add_item(session, item);
+
+  ud.frame_not_send_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(1 == ud.frame_not_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == ud.not_sent_frame_type);
+  CU_ASSERT(NGHTTP2_ERR_FRAME_SIZE_ERROR == ud.not_sent_error);
+
+  for (i = 0; i < nnv; ++i) {
+    free(nv[i].value);
+  }
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_headers_push_reply(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));
+  nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_RESERVED, NULL);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_headers_init(&frame->headers, NGHTTP2_FLAG_END_HEADERS, 2,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  nghttp2_session_add_item(session, item);
+  CU_ASSERT(0 == session->num_outgoing_streams);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == session->num_outgoing_streams);
+  stream = nghttp2_session_get_stream(session, 2);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_rst_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_rst_stream_init(&frame->rst_stream, 1, NGHTTP2_PROTOCOL_ERROR);
+  nghttp2_session_add_item(session, item);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_send_push_promise(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_stream *stream;
+  nghttp2_settings_entry iv;
+  my_user_data ud;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_push_promise_init(&frame->push_promise,
+                                  NGHTTP2_FLAG_END_HEADERS, 1,
+                                  session->next_stream_id, NULL, 0);
+
+  session->next_stream_id += 2;
+
+  nghttp2_session_add_item(session, item);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  stream = nghttp2_session_get_stream(session, 2);
+  CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
+
+  /* Received ENABLE_PUSH = 0 */
+  iv.settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  iv.value = 0;
+  frame = malloc(sizeof(nghttp2_frame));
+  nghttp2_frame_settings_init(&frame->settings, NGHTTP2_FLAG_NONE,
+                              dup_iv(&iv, 1), 1);
+  nghttp2_session_on_settings_received(session, frame, 1);
+  nghttp2_frame_settings_free(&frame->settings, mem);
+  free(frame);
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_push_promise_init(&frame->push_promise,
+                                  NGHTTP2_FLAG_END_HEADERS, 1, -1, NULL, 0);
+  nghttp2_session_add_item(session, item);
+
+  ud.frame_not_send_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(1 == ud.frame_not_send_cb_called);
+  CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.not_sent_frame_type);
+  CU_ASSERT(NGHTTP2_ERR_PUSH_DISABLED == ud.not_sent_error);
+
+  nghttp2_session_del(session);
+
+  /* PUSH_PROMISE from client is error */
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  item = malloc(sizeof(nghttp2_outbound_item));
+
+  nghttp2_session_outbound_item_init(session, item);
+
+  frame = &item->frame;
+
+  nghttp2_frame_push_promise_init(&frame->push_promise,
+                                  NGHTTP2_FLAG_END_HEADERS, 1, -1, NULL, 0);
+  nghttp2_session_add_item(session, item);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 3));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_is_my_stream_id(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0));
+  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 1));
+  CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 2));
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+
+  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 0));
+  CU_ASSERT(1 == nghttp2_session_is_my_stream_id(session, 1));
+  CU_ASSERT(0 == nghttp2_session_is_my_stream_id(session, 2));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_upgrade(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  uint8_t settings_payload[128];
+  size_t settings_payloadlen;
+  nghttp2_settings_entry iv[16];
+  nghttp2_stream *stream;
+  nghttp2_outbound_item *item;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = 1;
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 4095;
+  settings_payloadlen = nghttp2_pack_settings_payload(
+      settings_payload, sizeof(settings_payload), iv, 2);
+
+  /* Check client side */
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+  CU_ASSERT(0 == nghttp2_session_upgrade(session, settings_payload,
+                                         settings_payloadlen, &callbacks));
+  stream = nghttp2_session_get_stream(session, 1);
+  CU_ASSERT(stream != NULL);
+  CU_ASSERT(&callbacks == stream->stream_user_data);
+  CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type);
+  CU_ASSERT(2 == item->frame.settings.niv);
+  CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
+            item->frame.settings.iv[0].settings_id);
+  CU_ASSERT(1 == item->frame.settings.iv[0].value);
+  CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE ==
+            item->frame.settings.iv[1].settings_id);
+  CU_ASSERT(4095 == item->frame.settings.iv[1].value);
+
+  /* Call nghttp2_session_upgrade() again is error */
+  CU_ASSERT(NGHTTP2_ERR_PROTO ==
+            nghttp2_session_upgrade(session, settings_payload,
+                                    settings_payloadlen, &callbacks));
+  nghttp2_session_del(session);
+
+  /* Check server side */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  CU_ASSERT(0 == nghttp2_session_upgrade(session, settings_payload,
+                                         settings_payloadlen, &callbacks));
+  stream = nghttp2_session_get_stream(session, 1);
+  CU_ASSERT(stream != NULL);
+  CU_ASSERT(NULL == stream->stream_user_data);
+  CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags);
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+  CU_ASSERT(1 == session->remote_settings.max_concurrent_streams);
+  CU_ASSERT(4095 == session->remote_settings.initial_window_size);
+  /* Call nghttp2_session_upgrade() again is error */
+  CU_ASSERT(NGHTTP2_ERR_PROTO ==
+            nghttp2_session_upgrade(session, settings_payload,
+                                    settings_payloadlen, &callbacks));
+  nghttp2_session_del(session);
+
+  /* Empty SETTINGS is OK */
+  settings_payloadlen = nghttp2_pack_settings_payload(
+      settings_payload, sizeof(settings_payload), NULL, 0);
+
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+  CU_ASSERT(0 == nghttp2_session_upgrade(session, settings_payload,
+                                         settings_payloadlen, NULL));
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_reprioritize_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_stream *stream;
+  nghttp2_stream *dep_stream;
+  nghttp2_priority_spec pri_spec;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = block_count_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 10, 0);
+
+  nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+  CU_ASSERT(10 == stream->weight);
+  CU_ASSERT(NULL == stream->dep_prev);
+
+  /* If depenency to idle stream which is not in depdenency tree yet */
+
+  nghttp2_priority_spec_init(&pri_spec, 3, 99, 0);
+
+  nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+  CU_ASSERT(99 == stream->weight);
+  CU_ASSERT(3 == stream->dep_prev->stream_id);
+
+  dep_stream = nghttp2_session_get_stream_raw(session, 3);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == dep_stream->weight);
+
+  dep_stream = open_stream(session, 3);
+
+  /* Change weight */
+  pri_spec.weight = 128;
+
+  nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+  CU_ASSERT(128 == stream->weight);
+  CU_ASSERT(dep_stream == stream->dep_prev);
+
+  /* Test circular dependency; stream 1 is first removed and becomes
+     root.  Then stream 3 depends on it. */
+  nghttp2_priority_spec_init(&pri_spec, 1, 1, 0);
+
+  nghttp2_session_reprioritize_stream(session, dep_stream, &pri_spec);
+
+  CU_ASSERT(1 == dep_stream->weight);
+  CU_ASSERT(stream == dep_stream->dep_prev);
+
+  /* Making priority to closed stream will result in default
+     priority */
+  session->last_recv_stream_id = 9;
+
+  nghttp2_priority_spec_init(&pri_spec, 5, 5, 0);
+
+  nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *stream;
+  nghttp2_priority_spec pri_spec;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = block_count_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  session->pending_local_max_concurrent_stream = 1;
+
+  nghttp2_priority_spec_init(&pri_spec, 101, 10, 0);
+
+  nghttp2_session_reprioritize_stream(session, stream, &pri_spec);
+
+  /* idle stream is not counteed to max concurrent streams */
+
+  CU_ASSERT(10 == stream->weight);
+  CU_ASSERT(101 == stream->dep_prev->stream_id);
+
+  stream = nghttp2_session_get_stream_raw(session, 101);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+  nghttp2_frame *frame;
+  nghttp2_frame_hd hd;
+  nghttp2_active_outbound_item *aob;
+  nghttp2_bufs *framebufs;
+  nghttp2_buf *buf;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = block_count_send_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+  aob = &session->aob;
+  framebufs = &aob->framebufs;
+
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(
+      0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
+
+  ud.block_count = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  frame = &aob->item->frame;
+
+  buf = &framebufs->head->buf;
+  nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
+
+  CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
+  CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags);
+  /* aux_data.data.flags has these flags */
+  CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_data_read_length_too_large(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+  nghttp2_frame *frame;
+  nghttp2_frame_hd hd;
+  nghttp2_active_outbound_item *aob;
+  nghttp2_bufs *framebufs;
+  nghttp2_buf *buf;
+  size_t payloadlen;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = block_count_send_callback;
+  callbacks.read_length_callback = too_large_data_source_length_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+  aob = &session->aob;
+  framebufs = &aob->framebufs;
+
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(
+      0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
+
+  ud.block_count = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  frame = &aob->item->frame;
+
+  buf = &framebufs->head->buf;
+  nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
+
+  CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
+  CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags);
+  CU_ASSERT(16384 == hd.length)
+  /* aux_data.data.flags has these flags */
+  CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags);
+
+  nghttp2_session_del(session);
+
+  /* Check that buffers are expanded */
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+
+  ud.data_source_length = NGHTTP2_MAX_FRAME_SIZE_MAX;
+
+  session->remote_settings.max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MAX;
+
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(
+      0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
+
+  ud.block_count = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  aob = &session->aob;
+
+  frame = &aob->item->frame;
+
+  framebufs = &aob->framebufs;
+
+  buf = &framebufs->head->buf;
+  nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
+
+  payloadlen = nghttp2_min(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE,
+                           NGHTTP2_INITIAL_WINDOW_SIZE);
+
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN + 1 + payloadlen ==
+            (size_t)nghttp2_buf_cap(buf));
+  CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
+  CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags);
+  CU_ASSERT(payloadlen == hd.length);
+  /* aux_data.data.flags has these flags */
+  CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_data_read_length_smallest(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+  nghttp2_frame *frame;
+  nghttp2_frame_hd hd;
+  nghttp2_active_outbound_item *aob;
+  nghttp2_bufs *framebufs;
+  nghttp2_buf *buf;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = block_count_send_callback;
+  callbacks.read_length_callback = smallest_length_data_source_length_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+  aob = &session->aob;
+  framebufs = &aob->framebufs;
+
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(
+      0 == nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM, 1, &data_prd));
+
+  ud.block_count = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  frame = &aob->item->frame;
+
+  buf = &framebufs->head->buf;
+  nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
+
+  CU_ASSERT(NGHTTP2_FLAG_NONE == hd.flags);
+  CU_ASSERT(NGHTTP2_FLAG_NONE == frame->hd.flags);
+  CU_ASSERT(1 == hd.length)
+  /* aux_data.data.flags has these flags */
+  CU_ASSERT(NGHTTP2_FLAG_END_STREAM == aob->item->aux_data.data.flags);
+
+  nghttp2_session_del(session);
+}
+
+static ssize_t submit_data_twice_data_source_read_callback(
+    nghttp2_session *session _U_, int32_t stream_id _U_, uint8_t *buf _U_,
+    size_t len, uint32_t *data_flags, nghttp2_data_source *source _U_,
+    void *user_data _U_) {
+  *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+  return nghttp2_min(len, 16);
+}
+
+static int submit_data_twice_on_frame_send_callback(nghttp2_session *session,
+                                                    const nghttp2_frame *frame,
+                                                    void *user_data _U_) {
+  static int called = 0;
+  int rv;
+  nghttp2_data_provider data_prd;
+
+  if (called == 0) {
+    called = 1;
+
+    data_prd.read_callback = submit_data_twice_data_source_read_callback;
+
+    rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,
+                             frame->hd.stream_id, &data_prd);
+    CU_ASSERT(0 == rv);
+  }
+
+  return 0;
+}
+
+void test_nghttp2_submit_data_twice(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+  accumulator acc;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = accumulator_send_callback;
+  callbacks.on_frame_send_callback = submit_data_twice_on_frame_send_callback;
+
+  data_prd.read_callback = submit_data_twice_data_source_read_callback;
+
+  acc.length = 0;
+  ud.acc = &acc;
+
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &data_prd));
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* We should have sent 2 DATA frame with 16 bytes payload each */
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN * 2 + 16 * 2 == acc.length);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_request_with_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")};
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+  nghttp2_outbound_item *item;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = 64 * 1024 - 1;
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+  CU_ASSERT(1 == nghttp2_submit_request(session, NULL, nva, ARRLEN(nva),
+                                        &data_prd, NULL));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == ud.data_source_length);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_request_without_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  accumulator acc;
+  nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")};
+  nghttp2_data_provider data_prd = {{-1}, NULL};
+  nghttp2_outbound_item *item;
+  my_user_data ud;
+  nghttp2_frame frame;
+  nghttp2_hd_inflater inflater;
+  nva_out out;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  acc.length = 0;
+  ud.acc = &acc;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = accumulator_send_callback;
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+
+  nghttp2_hd_inflate_init(&inflater, mem);
+  CU_ASSERT(1 == nghttp2_submit_request(session, NULL, nva, ARRLEN(nva),
+                                        &data_prd, NULL));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length));
+
+  nghttp2_bufs_add(&bufs, acc.buf, acc.length);
+  inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN);
+
+  CU_ASSERT(nvnameeq(":version", &out.nva[0]));
+  nghttp2_frame_headers_free(&frame.headers, mem);
+  nva_out_reset(&out);
+
+  nghttp2_bufs_free(&bufs);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_response_with_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")};
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+  nghttp2_outbound_item *item;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = 64 * 1024 - 1;
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+  nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(0 ==
+            nghttp2_submit_response(session, 1, nva, ARRLEN(nva), &data_prd));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == ud.data_source_length);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_response_without_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  accumulator acc;
+  nghttp2_nv nva[] = {MAKE_NV(":version", "HTTP/1.1")};
+  nghttp2_data_provider data_prd = {{-1}, NULL};
+  nghttp2_outbound_item *item;
+  my_user_data ud;
+  nghttp2_frame frame;
+  nghttp2_hd_inflater inflater;
+  nva_out out;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  acc.length = 0;
+  ud.acc = &acc;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = accumulator_send_callback;
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+
+  nghttp2_hd_inflate_init(&inflater, mem);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_END_STREAM,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(0 ==
+            nghttp2_submit_response(session, 1, nva, ARRLEN(nva), &data_prd));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT(item->frame.hd.flags & NGHTTP2_FLAG_END_STREAM);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length));
+
+  nghttp2_bufs_add(&bufs, acc.buf, acc.length);
+  inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN);
+
+  CU_ASSERT(nvnameeq(":version", &out.nva[0]));
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_headers_free(&frame.headers, mem);
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_headers_start_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")};
+  nghttp2_outbound_item *item;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
+  CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1,
+                                        NULL, nv, ARRLEN(nv), NULL));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT((NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_END_STREAM) ==
+            item->frame.hd.flags);
+  CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_headers_reply(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")};
+  my_user_data ud;
+  nghttp2_outbound_item *item;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+                                        NULL, nv, ARRLEN(nv), NULL));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) ==
+            item->frame.hd.flags);
+
+  ud.frame_send_cb_called = 0;
+  ud.sent_frame_type = 0;
+  /* The transimission will be canceled because the stream 1 is not
+     open. */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == ud.frame_send_cb_called);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+                                        NULL, nv, ARRLEN(nv), NULL));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
+  CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_headers_push_reply(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")};
+  my_user_data ud;
+  nghttp2_stream *stream;
+  int foo;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, nv,
+                                        ARRLEN(nv), &foo));
+
+  ud.frame_send_cb_called = 0;
+  ud.sent_frame_type = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+  CU_ASSERT(&foo == stream->stream_user_data);
+
+  nghttp2_session_del(session);
+
+  /* Sending HEADERS from client against stream in reserved state is
+     error */
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 2, NULL, nv,
+                                        ARRLEN(nv), NULL));
+
+  ud.frame_send_cb_called = 0;
+  ud.sent_frame_type = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == ud.frame_send_cb_called);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_headers(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")};
+  my_user_data ud;
+  nghttp2_outbound_item *item;
+  nghttp2_stream *stream;
+  accumulator acc;
+  nghttp2_frame frame;
+  nghttp2_hd_inflater inflater;
+  nva_out out;
+  nghttp2_bufs bufs;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  frame_pack_bufs_init(&bufs);
+
+  nva_out_init(&out);
+  acc.length = 0;
+  ud.acc = &acc;
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = accumulator_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+
+  nghttp2_hd_inflate_init(&inflater, mem);
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+                                        NULL, nv, ARRLEN(nv), NULL));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(nvnameeq(":version", &item->frame.headers.nva[0]));
+  CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) ==
+            item->frame.hd.flags);
+
+  ud.frame_send_cb_called = 0;
+  ud.sent_frame_type = 0;
+  /* The transimission will be canceled because the stream 1 is not
+     open. */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == ud.frame_send_cb_called);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+                                        NULL, nv, ARRLEN(nv), NULL));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
+  CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR);
+
+  CU_ASSERT(0 == unpack_frame(&frame, acc.buf, acc.length));
+
+  nghttp2_bufs_add(&bufs, acc.buf, acc.length);
+  inflate_hd(&inflater, &out, &bufs, NGHTTP2_FRAME_HDLEN);
+
+  CU_ASSERT(nvnameeq(":version", &out.nva[0]));
+
+  nva_out_reset(&out);
+  nghttp2_bufs_free(&bufs);
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  nghttp2_hd_inflate_free(&inflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_headers_continuation(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_nv nv[] = {
+      MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""),
+      MAKE_NV("h1", ""), MAKE_NV("h1", ""), MAKE_NV("h1", ""),
+      MAKE_NV("h1", ""),
+  };
+  nghttp2_outbound_item *item;
+  uint8_t data[4096];
+  size_t i;
+  my_user_data ud;
+
+  memset(data, '0', sizeof(data));
+  for (i = 0; i < ARRLEN(nv); ++i) {
+    nv[i].valuelen = sizeof(data);
+    nv[i].value = data;
+  }
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
+  CU_ASSERT(1 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1,
+                                        NULL, nv, ARRLEN(nv), NULL));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+  CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_HEADERS) ==
+            item->frame.hd.flags);
+  CU_ASSERT(0 == (item->frame.hd.flags & NGHTTP2_FLAG_PRIORITY));
+
+  ud.frame_send_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_priority(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *stream;
+  my_user_data ud;
+  nghttp2_priority_spec pri_spec;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 3, 0);
+
+  /* depends on stream 0 */
+  CU_ASSERT(0 ==
+            nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(3 == stream->weight);
+
+  /* submit against idle stream */
+  CU_ASSERT(0 ==
+            nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 3, &pri_spec));
+
+  ud.frame_send_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_settings(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_outbound_item *item;
+  nghttp2_frame *frame;
+  nghttp2_settings_entry iv[7];
+  nghttp2_frame ack_frame;
+  const int32_t UNKNOWN_ID = 1000000007;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = 5;
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 16 * 1024;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[2].value = 50;
+
+  iv[3].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[3].value = 0;
+
+  iv[4].settings_id = UNKNOWN_ID;
+  iv[4].value = 999;
+
+  iv[5].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[5].value = (uint32_t)NGHTTP2_MAX_WINDOW_SIZE + 1;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
+            nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 6));
+
+  /* Make sure that local settings are not changed */
+  CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ==
+            session->local_settings.max_concurrent_streams);
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
+            session->local_settings.initial_window_size);
+
+  /* Now sends without 6th one */
+  CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 5));
+
+  item = nghttp2_session_get_next_ob_item(session);
+
+  CU_ASSERT(NGHTTP2_SETTINGS == item->frame.hd.type);
+
+  frame = &item->frame;
+  CU_ASSERT(5 == frame->settings.niv);
+  CU_ASSERT(5 == frame->settings.iv[0].value);
+  CU_ASSERT(NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS ==
+            frame->settings.iv[0].settings_id);
+
+  CU_ASSERT(16 * 1024 == frame->settings.iv[1].value);
+  CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE ==
+            frame->settings.iv[1].settings_id);
+
+  CU_ASSERT(UNKNOWN_ID == frame->settings.iv[4].settings_id);
+  CU_ASSERT(999 == frame->settings.iv[4].value);
+
+  ud.frame_send_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+
+  CU_ASSERT(50 == session->pending_local_max_concurrent_stream);
+
+  nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
+  nghttp2_frame_settings_free(&ack_frame.settings, mem);
+
+  CU_ASSERT(16 * 1024 == session->local_settings.initial_window_size);
+  CU_ASSERT(0 == session->hd_inflater.ctx.hd_table_bufsize_max);
+  CU_ASSERT(50 == session->local_settings.max_concurrent_streams);
+  CU_ASSERT(NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS ==
+            session->pending_local_max_concurrent_stream);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_settings_update_local_window_size(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_settings_entry iv[4];
+  nghttp2_stream *stream;
+  nghttp2_frame ack_frame;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  nghttp2_frame_settings_init(&ack_frame.settings, NGHTTP2_FLAG_ACK, NULL, 0);
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[0].value = 16 * 1024;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+  stream->local_window_size = NGHTTP2_INITIAL_WINDOW_SIZE + 100;
+  stream->recv_window_size = 32768;
+
+  stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+
+  CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
+
+  stream = nghttp2_session_get_stream(session, 1);
+  CU_ASSERT(0 == stream->recv_window_size);
+  CU_ASSERT(16 * 1024 + 100 == stream->local_window_size);
+
+  stream = nghttp2_session_get_stream(session, 3);
+  CU_ASSERT(16 * 1024 == stream->local_window_size);
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(32768 == item->frame.window_update.window_size_increment);
+
+  nghttp2_session_del(session);
+
+  /* Check overflow case */
+  iv[0].value = 128 * 1024;
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+  stream->local_window_size = NGHTTP2_MAX_WINDOW_SIZE;
+
+  CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &ack_frame, 0));
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_FLOW_CONTROL_ERROR == item->frame.goaway.error_code);
+
+  nghttp2_session_del(session);
+  nghttp2_frame_settings_free(&ack_frame.settings, mem);
+}
+
+void test_nghttp2_submit_push_promise(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const nghttp2_nv nv[] = {MAKE_NV(":version", "HTTP/1.1")};
+  my_user_data ud;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(2 == nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1, nv,
+                                             ARRLEN(nv), &ud));
+
+  ud.frame_send_cb_called = 0;
+  ud.sent_frame_type = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_PUSH_PROMISE == ud.sent_frame_type);
+  stream = nghttp2_session_get_stream(session, 2);
+  CU_ASSERT(NGHTTP2_STREAM_RESERVED == stream->state);
+  CU_ASSERT(&ud == nghttp2_session_get_stream_user_data(session, 2));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_window_update(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_outbound_item *item;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+  stream->recv_window_size = 4096;
+
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 1024));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(1024 == item->frame.window_update.window_size_increment);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(3072 == stream->recv_window_size);
+
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(4096 == item->frame.window_update.window_size_increment);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == stream->recv_window_size);
+
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 4096));
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(4096 == item->frame.window_update.window_size_increment);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == stream->recv_window_size);
+
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 0));
+  /* It is ok if stream is closed or does not exist at the call
+     time */
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 4, 4096));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_window_update_local_window_size(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+  stream->recv_window_size = 4096;
+
+  CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2,
+                                              stream->recv_window_size + 1));
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1 == stream->local_window_size);
+  CU_ASSERT(0 == stream->recv_window_size);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(4097 == item->frame.window_update.window_size_increment);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Let's decrement local window size */
+  stream->recv_window_size = 4096;
+  CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2,
+                                              -stream->local_window_size / 2));
+  CU_ASSERT(32768 == stream->local_window_size);
+  CU_ASSERT(-28672 == stream->recv_window_size);
+  CU_ASSERT(32768 == stream->recv_reduction);
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(item == NULL);
+
+  /* Increase local window size */
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2, 16384));
+  CU_ASSERT(49152 == stream->local_window_size);
+  CU_ASSERT(-12288 == stream->recv_window_size);
+  CU_ASSERT(16384 == stream->recv_reduction);
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 2,
+                                         NGHTTP2_MAX_WINDOW_SIZE));
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Check connection-level flow control */
+  session->recv_window_size = 4096;
+  CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0,
+                                              session->recv_window_size + 1));
+  CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1 ==
+            session->local_window_size);
+  CU_ASSERT(0 == session->recv_window_size);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(4097 == item->frame.window_update.window_size_increment);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Go decrement part */
+  session->recv_window_size = 4096;
+  CU_ASSERT(0 == nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0,
+                                              -session->local_window_size / 2));
+  CU_ASSERT(32768 == session->local_window_size);
+  CU_ASSERT(-28672 == session->recv_window_size);
+  CU_ASSERT(32768 == session->recv_reduction);
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(item == NULL);
+
+  /* Increase local window size */
+  CU_ASSERT(0 ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 16384));
+  CU_ASSERT(49152 == session->local_window_size);
+  CU_ASSERT(-12288 == session->recv_window_size);
+  CU_ASSERT(16384 == session->recv_reduction);
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  CU_ASSERT(NGHTTP2_ERR_FLOW_CONTROL ==
+            nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0,
+                                         NGHTTP2_MAX_WINDOW_SIZE));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_shutdown_notice(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session));
+
+  ud.frame_send_cb_called = 0;
+
+  nghttp2_session_send(session);
+
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_GOAWAY == ud.sent_frame_type);
+  CU_ASSERT((1u << 31) - 1 == session->local_last_stream_id);
+
+  /* After another GOAWAY, nghttp2_submit_shutdown_notice() is
+     noop. */
+  CU_ASSERT(0 == nghttp2_session_terminate_session(session, NGHTTP2_NO_ERROR));
+
+  ud.frame_send_cb_called = 0;
+
+  nghttp2_session_send(session);
+
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_GOAWAY == ud.sent_frame_type);
+  CU_ASSERT(0 == session->local_last_stream_id);
+
+  CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session));
+
+  ud.frame_send_cb_called = 0;
+  ud.frame_not_send_cb_called = 0;
+
+  nghttp2_session_send(session);
+
+  CU_ASSERT(0 == ud.frame_send_cb_called);
+  CU_ASSERT(0 == ud.frame_not_send_cb_called);
+
+  nghttp2_session_del(session);
+
+  /* Using nghttp2_submit_shutdown_notice() with client side session
+     is error */
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+
+  CU_ASSERT(NGHTTP2_ERR_INVALID_STATE ==
+            nghttp2_submit_shutdown_notice(session));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_submit_invalid_nv(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_nv empty_name_nv[] = {MAKE_NV("Version", "HTTP/1.1"),
+                                MAKE_NV("", "empty name")};
+
+  /* Now invalid header name/value pair in HTTP/1.1 is accepted in
+     nghttp2 */
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));
+
+  /* nghttp2_submit_request */
+  CU_ASSERT(0 < nghttp2_submit_request(session, NULL, empty_name_nv,
+                                       ARRLEN(empty_name_nv), NULL, NULL));
+
+  /* nghttp2_submit_response */
+  CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv,
+                                         ARRLEN(empty_name_nv), NULL));
+
+  /* nghttp2_submit_headers */
+  CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL,
+                                       empty_name_nv, ARRLEN(empty_name_nv),
+                                       NULL));
+
+  /* nghttp2_submit_push_promise */
+  open_stream(session, 1);
+
+  CU_ASSERT(0 < nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 1,
+                                            empty_name_nv,
+                                            ARRLEN(empty_name_nv), NULL));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_open_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *stream;
+  nghttp2_priority_spec pri_spec;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 245, 0);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
+  CU_ASSERT(1 == session->num_incoming_streams);
+  CU_ASSERT(0 == session->num_outgoing_streams);
+  CU_ASSERT(NGHTTP2_STREAM_OPENED == stream->state);
+  CU_ASSERT(245 == stream->weight);
+  CU_ASSERT(NULL == stream->dep_prev);
+  CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags);
+
+  stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(1 == session->num_incoming_streams);
+  CU_ASSERT(1 == session->num_outgoing_streams);
+  CU_ASSERT(NULL == stream->dep_prev);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+  CU_ASSERT(NGHTTP2_SHUT_NONE == stream->shut_flags);
+
+  stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  CU_ASSERT(1 == session->num_incoming_streams);
+  CU_ASSERT(1 == session->num_outgoing_streams);
+  CU_ASSERT(NULL == stream->dep_prev);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+  CU_ASSERT(NGHTTP2_SHUT_RD == stream->shut_flags);
+
+  nghttp2_priority_spec_init(&pri_spec, 1, 17, 1);
+
+  stream = nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
+  CU_ASSERT(17 == stream->weight);
+  CU_ASSERT(1 == stream->dep_prev->stream_id);
+
+  /* Dependency to idle stream */
+  nghttp2_priority_spec_init(&pri_spec, 1000000007, 240, 1);
+
+  stream = nghttp2_session_open_stream(session, 5, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
+  CU_ASSERT(240 == stream->weight);
+  CU_ASSERT(1000000007 == stream->dep_prev->stream_id);
+
+  stream = nghttp2_session_get_stream_raw(session, 1000000007);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+  CU_ASSERT(NULL != stream->root_next);
+
+  /* Dependency to closed stream which is not in dependency tree */
+  session->last_recv_stream_id = 7;
+
+  nghttp2_priority_spec_init(&pri_spec, 7, 10, 0);
+
+  stream = nghttp2_session_open_stream(session, 9, NGHTTP2_FLAG_NONE, &pri_spec,
+                                       NGHTTP2_STREAM_OPENED, NULL);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+  stream = nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_RESERVED, NULL);
+  CU_ASSERT(0 == session->num_incoming_streams);
+  CU_ASSERT(0 == session->num_outgoing_streams);
+  CU_ASSERT(NULL == stream->dep_prev);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+  CU_ASSERT(NGHTTP2_SHUT_WR == stream->shut_flags);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_open_stream_with_idle_stream_dep(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *stream;
+  nghttp2_priority_spec pri_spec;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  /* Dependency to idle stream */
+  nghttp2_priority_spec_init(&pri_spec, 101, 245, 0);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
+
+  CU_ASSERT(245 == stream->weight);
+  CU_ASSERT(101 == stream->dep_prev->stream_id);
+
+  stream = nghttp2_session_get_stream_raw(session, 101);
+
+  CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+
+  nghttp2_priority_spec_init(&pri_spec, 211, 1, 0);
+
+  /* stream 101 was already created as idle. */
+  stream = nghttp2_session_open_stream(session, 101, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec, NGHTTP2_STREAM_OPENED, NULL);
+
+  CU_ASSERT(1 == stream->weight);
+  CU_ASSERT(211 == stream->dep_prev->stream_id);
+
+  stream = nghttp2_session_get_stream_raw(session, 211);
+
+  CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_get_next_ob_item(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_priority_spec pri_spec;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  session->remote_settings.max_concurrent_streams = 2;
+
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+  nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL);
+  CU_ASSERT(NGHTTP2_PING ==
+            nghttp2_session_get_next_ob_item(session)->frame.hd.type);
+
+  nghttp2_submit_request(session, NULL, NULL, 0, NULL, NULL);
+  CU_ASSERT(NGHTTP2_PING ==
+            nghttp2_session_get_next_ob_item(session)->frame.hd.type);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  /* Incoming stream does not affect the number of outgoing max
+     concurrent streams. */
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0);
+
+  nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL);
+  CU_ASSERT(NGHTTP2_HEADERS ==
+            nghttp2_session_get_next_ob_item(session)->frame.hd.type);
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL);
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
+
+  session->remote_settings.max_concurrent_streams = 3;
+
+  CU_ASSERT(NGHTTP2_HEADERS ==
+            nghttp2_session_get_next_ob_item(session)->frame.hd.type);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_pop_next_ob_item(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_outbound_item *item;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_stream *stream;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  session->remote_settings.max_concurrent_streams = 1;
+
+  CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session));
+
+  nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 254, 0);
+
+  nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL);
+
+  item = nghttp2_session_pop_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_PING == item->frame.hd.type);
+  nghttp2_outbound_item_free(item, mem);
+  free(item);
+
+  item = nghttp2_session_pop_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+  nghttp2_outbound_item_free(item, mem);
+  free(item);
+
+  CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session));
+
+  /* Incoming stream does not affect the number of outgoing max
+     concurrent streams. */
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  /* In-flight outgoing stream */
+  nghttp2_session_open_stream(session, 4, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, NGHTTP2_MAX_WEIGHT, 0);
+
+  nghttp2_submit_request(session, &pri_spec, NULL, 0, NULL, NULL);
+  nghttp2_submit_response(session, 1, NULL, 0, NULL);
+
+  item = nghttp2_session_pop_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+  CU_ASSERT(1 == item->frame.hd.stream_id);
+
+  stream = nghttp2_session_get_stream(session, 1);
+
+  nghttp2_stream_detach_item(stream, session);
+
+  nghttp2_outbound_item_free(item, mem);
+  free(item);
+
+  CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session));
+
+  session->remote_settings.max_concurrent_streams = 2;
+
+  item = nghttp2_session_pop_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
+  nghttp2_outbound_item_free(item, mem);
+  free(item);
+
+  nghttp2_session_del(session);
+
+  /* Check that push reply HEADERS are queued into ob_ss_pq */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  session->remote_settings.max_concurrent_streams = 0;
+  nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_RESERVED, NULL);
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 2,
+                                        NULL, NULL, 0, NULL));
+  CU_ASSERT(NULL == nghttp2_session_pop_next_ob_item(session));
+  CU_ASSERT(1 == nghttp2_pq_size(&session->ob_ss_pq));
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_reply_fail(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_data_provider data_prd;
+  my_user_data ud;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = fail_send_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+  ud.data_source_length = 4 * 1024;
+  CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  CU_ASSERT(0 == nghttp2_submit_response(session, 1, NULL, 0, &data_prd));
+  CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session));
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_max_concurrent_streams(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_frame frame;
+  nghttp2_outbound_item *item;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENED, NULL);
+
+  /* Check un-ACKed SETTINGS_MAX_CONCURRENT_STREAMS */
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 3,
+                             NGHTTP2_HCAT_HEADERS, NULL, NULL, 0);
+  session->pending_local_max_concurrent_stream = 1;
+
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+
+  item = nghttp2_session_get_ob_pq_top(session);
+  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_REFUSED_STREAM == item->frame.rst_stream.error_code);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Check ACKed SETTINGS_MAX_CONCURRENT_STREAMS */
+  session->local_settings.max_concurrent_streams = 1;
+  frame.hd.stream_id = 5;
+
+  CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
+            nghttp2_session_on_request_headers_received(session, &frame));
+
+  item = nghttp2_session_get_ob_pq_top(session);
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
+  CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == item->frame.goaway.error_code);
+
+  nghttp2_frame_headers_free(&frame.headers, mem);
+  nghttp2_session_del(session);
+}
+
+/*
+ * Check that on_stream_close_callback is called when server pushed
+ * HEADERS have NGHTTP2_FLAG_END_STREAM.
+ */
+void test_nghttp2_session_stream_close_on_headers_push(void) {
+  /* nghttp2_session *session; */
+  /* nghttp2_session_callbacks callbacks; */
+  /* const char *nv[] = { NULL }; */
+  /* my_user_data ud; */
+  /* nghttp2_frame frame; */
+
+  /* memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); */
+  /* callbacks.on_stream_close_callback = */
+  /*   no_stream_user_data_stream_close_callback; */
+  /* ud.stream_close_cb_called = 0; */
+
+  /* nghttp2_session_client_new(&session, NGHTTP2_PROTO_SPDY2, &callbacks, &ud);
+   */
+  /* nghttp2_session_open_stream(session, 1, NGHTTP2_CTRL_FLAG_NONE, 3, */
+  /*                             NGHTTP2_STREAM_OPENING, NULL); */
+  /* nghttp2_frame_syn_stream_init(&frame.syn_stream, NGHTTP2_PROTO_SPDY2, */
+  /*                               NGHTTP2_CTRL_FLAG_FIN | */
+  /*                               NGHTTP2_CTRL_FLAG_UNIDIRECTIONAL, */
+  /*                               2, 1, 3, dup_nv(nv)); */
+
+  /* CU_ASSERT(0 == nghttp2_session_on_request_headers_received(session,
+   * &frame)); */
+
+  /* nghttp2_frame_syn_stream_free(&frame.syn_stream); */
+  /* nghttp2_session_del(session); */
+}
+
+void test_nghttp2_session_stop_data_with_rst_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_data_provider data_prd;
+  nghttp2_frame frame;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  callbacks.send_callback = block_count_send_callback;
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+
+  ud.frame_send_cb_called = 0;
+  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  nghttp2_submit_response(session, 1, NULL, 0, &data_prd);
+
+  ud.block_count = 2;
+  /* Sends response HEADERS + DATA[0] */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type);
+  /* data for DATA[1] is read from data_prd but it is not sent */
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2);
+
+  nghttp2_frame_rst_stream_init(&frame.rst_stream, 1, NGHTTP2_CANCEL);
+  CU_ASSERT(0 == nghttp2_session_on_rst_stream_received(session, &frame));
+  nghttp2_frame_rst_stream_free(&frame.rst_stream);
+
+  /* Big enough number to send all DATA frames potentially. */
+  ud.block_count = 100;
+  /* Nothing will be sent in the following call. */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  /* With RST_STREAM, stream is canceled and further DATA on that
+     stream are not sent. */
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2);
+
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 1));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_defer_data(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_data_provider data_prd;
+  nghttp2_outbound_item *item;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  callbacks.send_callback = block_count_send_callback;
+  data_prd.read_callback = defer_data_source_read_callback;
+
+  ud.frame_send_cb_called = 0;
+  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+  nghttp2_submit_response(session, 1, NULL, 0, &data_prd);
+
+  ud.block_count = 1;
+  /* Sends HEADERS reply */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
+  /* No data is read */
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 4);
+
+  ud.block_count = 1;
+  nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL);
+  /* Sends PING */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_PING == ud.sent_frame_type);
+
+  /* Resume deferred DATA */
+  CU_ASSERT(0 == nghttp2_session_resume_data(session, 1));
+  item = (nghttp2_outbound_item *)nghttp2_pq_top(&session->ob_da_pq);
+  item->aux_data.data.data_prd.read_callback =
+      fixed_length_data_source_read_callback;
+  ud.block_count = 1;
+  /* Reads 2 DATA chunks */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2);
+
+  /* Deferred again */
+  item->aux_data.data.data_prd.read_callback = defer_data_source_read_callback;
+  /* This is needed since 4KiB block is already read and waiting to be
+     sent. No read_callback invocation. */
+  ud.block_count = 1;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2);
+
+  /* Resume deferred DATA */
+  CU_ASSERT(0 == nghttp2_session_resume_data(session, 1));
+  item = (nghttp2_outbound_item *)nghttp2_pq_top(&session->ob_da_pq);
+  item->aux_data.data.data_prd.read_callback =
+      fixed_length_data_source_read_callback;
+  ud.block_count = 1;
+  /* Reads 2 4KiB blocks */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(ud.data_source_length == 0);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_flow_control(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_data_provider data_prd;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  int32_t new_initial_window_size;
+  nghttp2_settings_entry iv[1];
+  nghttp2_frame settings_frame;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = fixed_bytes_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+
+  ud.frame_send_cb_called = 0;
+  ud.data_source_length = 128 * 1024;
+  /* Use smaller emission count so that we can check outbound flow
+     control window calculation is correct. */
+  ud.fixed_sendlen = 2 * 1024;
+
+  /* Initial window size to 64KiB - 1*/
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  /* Change it to 64KiB for easy calculation */
+  session->remote_window_size = 64 * 1024;
+  session->remote_settings.initial_window_size = 64 * 1024;
+
+  nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL);
+
+  /* Sends 64KiB - 1 data */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(64 * 1024 == ud.data_source_length);
+
+  /* Back 32KiB in stream window */
+  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1,
+                                   32 * 1024);
+  nghttp2_session_on_window_update_received(session, &frame);
+
+  /* Send nothing because of connection-level window */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(64 * 1024 == ud.data_source_length);
+
+  /* Back 32KiB in connection-level window */
+  frame.hd.stream_id = 0;
+  nghttp2_session_on_window_update_received(session, &frame);
+
+  /* Sends another 32KiB data */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(32 * 1024 == ud.data_source_length);
+
+  stream = nghttp2_session_get_stream(session, 1);
+  /* Change initial window size to 16KiB. The window_size becomes
+     negative. */
+  new_initial_window_size = 16 * 1024;
+  stream->remote_window_size =
+      new_initial_window_size - (session->remote_settings.initial_window_size -
+                                 stream->remote_window_size);
+  session->remote_settings.initial_window_size = new_initial_window_size;
+  CU_ASSERT(-48 * 1024 == stream->remote_window_size);
+
+  /* Back 48KiB to stream window */
+  frame.hd.stream_id = 1;
+  frame.window_update.window_size_increment = 48 * 1024;
+  nghttp2_session_on_window_update_received(session, &frame);
+
+  /* Nothing is sent because window_size is 0 */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(32 * 1024 == ud.data_source_length);
+
+  /* Back 16KiB in stream window */
+  frame.hd.stream_id = 1;
+  frame.window_update.window_size_increment = 16 * 1024;
+  nghttp2_session_on_window_update_received(session, &frame);
+
+  /* Back 24KiB in connection-level window */
+  frame.hd.stream_id = 0;
+  frame.window_update.window_size_increment = 24 * 1024;
+  nghttp2_session_on_window_update_received(session, &frame);
+
+  /* Sends another 16KiB data */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(16 * 1024 == ud.data_source_length);
+
+  /* Increase initial window size to 32KiB */
+  iv[0].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[0].value = 32 * 1024;
+
+  nghttp2_frame_settings_init(&settings_frame.settings, NGHTTP2_FLAG_NONE,
+                              dup_iv(iv, 1), 1);
+  nghttp2_session_on_settings_received(session, &settings_frame, 1);
+  nghttp2_frame_settings_free(&settings_frame.settings, mem);
+
+  /* Sends another 8KiB data */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(8 * 1024 == ud.data_source_length);
+
+  /* Back 8KiB in connection-level window */
+  frame.hd.stream_id = 0;
+  frame.window_update.window_size_increment = 8 * 1024;
+  nghttp2_session_on_window_update_received(session, &frame);
+
+  /* Sends last 8KiB data */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(0 == ud.data_source_length);
+  CU_ASSERT(nghttp2_session_get_stream(session, 1)->shut_flags &
+            NGHTTP2_SHUT_WR);
+
+  nghttp2_frame_window_update_free(&frame.window_update);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_flow_control_data_recv(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  uint8_t data[64 * 1024 + 16];
+  nghttp2_frame_hd hd;
+  nghttp2_outbound_item *item;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  /* Initial window size to 64KiB - 1*/
+  nghttp2_session_client_new(&session, &callbacks, NULL);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+
+  session->next_stream_id = 3;
+
+  nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_WR);
+
+  session->local_window_size = NGHTTP2_MAX_PAYLOADLEN;
+  stream->local_window_size = NGHTTP2_MAX_PAYLOADLEN;
+
+  /* Create DATA frame */
+  memset(data, 0, sizeof(data));
+  nghttp2_frame_hd_init(&hd, NGHTTP2_MAX_PAYLOADLEN, NGHTTP2_DATA,
+                        NGHTTP2_FLAG_END_STREAM, 1);
+
+  nghttp2_frame_pack_frame_hd(data, &hd);
+  CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN ==
+            nghttp2_session_mem_recv(session, data, NGHTTP2_MAX_PAYLOADLEN +
+                                                        NGHTTP2_FRAME_HDLEN));
+
+  item = nghttp2_session_get_next_ob_item(session);
+  /* Since this is the last frame, stream-level WINDOW_UPDATE is not
+     issued, but connection-level is. */
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(0 == item->frame.hd.stream_id);
+  CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN ==
+            item->frame.window_update.window_size_increment);
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  /* Receive DATA for closed stream. They are still subject to under
+     connection-level flow control, since this situation arises when
+     RST_STREAM is issued by the remote, but the local side keeps
+     sending DATA frames. Without calculating connection-level window,
+     the subsequent flow control gets confused. */
+  CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN + NGHTTP2_FRAME_HDLEN ==
+            nghttp2_session_mem_recv(session, data, NGHTTP2_MAX_PAYLOADLEN +
+                                                        NGHTTP2_FRAME_HDLEN));
+
+  item = nghttp2_session_get_next_ob_item(session);
+  CU_ASSERT(NGHTTP2_WINDOW_UPDATE == item->frame.hd.type);
+  CU_ASSERT(0 == item->frame.hd.stream_id);
+  CU_ASSERT(NGHTTP2_MAX_PAYLOADLEN ==
+            item->frame.window_update.window_size_increment);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_flow_control_data_with_padding_recv(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  uint8_t data[1024];
+  nghttp2_frame_hd hd;
+  nghttp2_stream *stream;
+  nghttp2_option *option;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_option_new(&option);
+  /* Disable auto window update so that we can check padding is
+     consumed automatically */
+  nghttp2_option_set_no_auto_window_update(option, 1);
+
+  /* Initial window size to 64KiB - 1*/
+  nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+
+  nghttp2_option_del(option);
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+
+  /* Create DATA frame */
+  memset(data, 0, sizeof(data));
+  nghttp2_frame_hd_init(&hd, 357, NGHTTP2_DATA,
+                        NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_PADDED, 1);
+
+  nghttp2_frame_pack_frame_hd(data, &hd);
+  /* Set Pad Length field, which itself is padding */
+  data[NGHTTP2_FRAME_HDLEN] = 255;
+
+  CU_ASSERT(
+      (ssize_t)(NGHTTP2_FRAME_HDLEN + hd.length) ==
+      nghttp2_session_mem_recv(session, data, NGHTTP2_FRAME_HDLEN + hd.length));
+
+  CU_ASSERT((int32_t)hd.length == session->recv_window_size);
+  CU_ASSERT((int32_t)hd.length == stream->recv_window_size);
+  CU_ASSERT(256 == session->consumed_size);
+  CU_ASSERT(256 == stream->consumed_size);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_data_read_temporal_failure(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_data_provider data_prd;
+  nghttp2_frame frame;
+  nghttp2_stream *stream;
+  size_t data_size = 128 * 1024;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+
+  ud.data_source_length = data_size;
+
+  /* Initial window size is 64KiB - 1 */
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL);
+
+  /* Sends NGHTTP2_INITIAL_WINDOW_SIZE data, assuming, it is equal to
+     or smaller than NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length);
+
+  stream = nghttp2_session_get_stream(session, 1);
+  CU_ASSERT(nghttp2_stream_check_deferred_by_flow_control(stream));
+  CU_ASSERT(NGHTTP2_DATA == stream->item->frame.hd.type);
+
+  stream->item->aux_data.data.data_prd.read_callback =
+      temporal_failure_data_source_read_callback;
+
+  /* Back NGHTTP2_INITIAL_WINDOW_SIZE to both connection-level and
+     stream-wise window */
+  nghttp2_frame_window_update_init(&frame.window_update, NGHTTP2_FLAG_NONE, 1,
+                                   NGHTTP2_INITIAL_WINDOW_SIZE);
+  nghttp2_session_on_window_update_received(session, &frame);
+  frame.hd.stream_id = 0;
+  nghttp2_session_on_window_update_received(session, &frame);
+  nghttp2_frame_window_update_free(&frame.window_update);
+
+  /* Sending data will fail (soft fail) and treated as stream error */
+  ud.frame_send_cb_called = 0;
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(data_size - NGHTTP2_INITIAL_WINDOW_SIZE == ud.data_source_length);
+
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(NGHTTP2_RST_STREAM == ud.sent_frame_type);
+
+  data_prd.read_callback = fail_data_source_read_callback;
+  nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL);
+  /* Sending data will fail (hard fail) and session tear down */
+  CU_ASSERT(NGHTTP2_ERR_CALLBACK_FAILURE == nghttp2_session_send(session));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_stream_close(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_stream_close_callback = on_stream_close_callback;
+  user_data.stream_close_cb_called = 0;
+
+  nghttp2_session_client_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       &user_data);
+  CU_ASSERT(stream != NULL);
+  CU_ASSERT(nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR) == 0);
+  CU_ASSERT(user_data.stream_close_cb_called == 1);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_ctrl_not_send(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data user_data;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.on_frame_not_send_callback = on_frame_not_send_callback;
+  callbacks.send_callback = null_send_callback;
+  user_data.frame_not_send_cb_called = 0;
+  user_data.not_sent_frame_type = 0;
+  user_data.not_sent_error = 0;
+
+  nghttp2_session_server_new(&session, &callbacks, &user_data);
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default,
+                                       NGHTTP2_STREAM_OPENING, &user_data);
+
+  /* Check response HEADERS */
+  /* Send bogus stream ID */
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 3,
+                                        NULL, NULL, 0, NULL));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
+  CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == user_data.not_sent_error);
+
+  user_data.frame_not_send_cb_called = 0;
+  /* Shutdown transmission */
+  stream->shut_flags |= NGHTTP2_SHUT_WR;
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+                                        NULL, NULL, 0, NULL));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
+  CU_ASSERT(NGHTTP2_ERR_STREAM_SHUT_WR == user_data.not_sent_error);
+
+  stream->shut_flags = NGHTTP2_SHUT_NONE;
+  user_data.frame_not_send_cb_called = 0;
+  /* Queue RST_STREAM */
+  CU_ASSERT(0 == nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, 1,
+                                        NULL, NULL, 0, NULL));
+  CU_ASSERT(0 == nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 1,
+                                           NGHTTP2_INTERNAL_ERROR));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
+  CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSING == user_data.not_sent_error);
+
+  nghttp2_session_del(session);
+
+  /* Check request HEADERS */
+  user_data.frame_not_send_cb_called = 0;
+  CU_ASSERT(nghttp2_session_client_new(&session, &callbacks, &user_data) == 0);
+  /* Maximum Stream ID is reached */
+  session->next_stream_id = (1u << 31) + 1;
+  CU_ASSERT(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE ==
+            nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1, NULL,
+                                   NULL, 0, NULL));
+
+  user_data.frame_not_send_cb_called = 0;
+  /* GOAWAY received */
+  session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
+  session->next_stream_id = 9;
+
+  CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1,
+                                       NULL, NULL, 0, NULL));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(1 == user_data.frame_not_send_cb_called);
+  CU_ASSERT(NGHTTP2_HEADERS == user_data.not_sent_frame_type);
+  CU_ASSERT(NGHTTP2_ERR_START_STREAM_NOT_ALLOWED == user_data.not_sent_error);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_get_outbound_queue_size(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
+  CU_ASSERT(0 == nghttp2_session_get_outbound_queue_size(session));
+
+  CU_ASSERT(0 == nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL));
+  CU_ASSERT(1 == nghttp2_session_get_outbound_queue_size(session));
+
+  CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 2,
+                                       NGHTTP2_NO_ERROR, NULL, 0));
+  CU_ASSERT(2 == nghttp2_session_get_outbound_queue_size(session));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_get_effective_local_window_size(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
+
+  stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                                       &pri_spec_default, NGHTTP2_STREAM_OPENED,
+                                       NULL);
+
+  CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE ==
+            nghttp2_session_get_effective_local_window_size(session));
+  CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session));
+
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE ==
+            nghttp2_session_get_stream_effective_local_window_size(session, 1));
+  CU_ASSERT(0 ==
+            nghttp2_session_get_stream_effective_recv_data_length(session, 1));
+
+  /* Check connection flow control */
+  session->recv_window_size = 100;
+  nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 1100);
+
+  CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1000 ==
+            nghttp2_session_get_effective_local_window_size(session));
+  CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session));
+
+  nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, -50);
+  /* Now session->recv_window_size = -50 */
+  CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 950 ==
+            nghttp2_session_get_effective_local_window_size(session));
+  CU_ASSERT(0 == nghttp2_session_get_effective_recv_data_length(session));
+
+  session->recv_window_size += 50;
+  /* Now session->recv_window_size = 0 */
+  nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 0, 100);
+  CU_ASSERT(NGHTTP2_INITIAL_CONNECTION_WINDOW_SIZE + 1050 ==
+            nghttp2_session_get_effective_local_window_size(session));
+  CU_ASSERT(50 == nghttp2_session_get_effective_recv_data_length(session));
+
+  /* Check stream flow control */
+  stream->recv_window_size = 100;
+  nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, 1100);
+
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1000 ==
+            nghttp2_session_get_stream_effective_local_window_size(session, 1));
+  CU_ASSERT(0 ==
+            nghttp2_session_get_stream_effective_recv_data_length(session, 1));
+
+  nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, -50);
+  /* Now stream->recv_window_size = -50 */
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 950 ==
+            nghttp2_session_get_stream_effective_local_window_size(session, 1));
+  CU_ASSERT(0 ==
+            nghttp2_session_get_stream_effective_recv_data_length(session, 1));
+
+  stream->recv_window_size += 50;
+  /* Now stream->recv_window_size = 0 */
+  nghttp2_submit_window_update(session, NGHTTP2_FLAG_NONE, 1, 100);
+  CU_ASSERT(NGHTTP2_INITIAL_WINDOW_SIZE + 1050 ==
+            nghttp2_session_get_stream_effective_local_window_size(session, 1));
+  CU_ASSERT(50 ==
+            nghttp2_session_get_stream_effective_recv_data_length(session, 1));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_set_option(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_option *option;
+
+  nghttp2_option_new(&option);
+
+  nghttp2_option_set_no_auto_window_update(option, 1);
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+
+  CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE);
+
+  nghttp2_session_del(session);
+
+  nghttp2_option_set_peer_max_concurrent_streams(option, 100);
+
+  nghttp2_session_client_new2(&session, &callbacks, NULL, option);
+
+  CU_ASSERT(100 == session->remote_settings.max_concurrent_streams);
+  nghttp2_session_del(session);
+
+  nghttp2_option_del(option);
+}
+
+void test_nghttp2_session_data_backoff_by_high_pri_frame(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_data_provider data_prd;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+  callbacks.send_callback = block_count_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+
+  ud.frame_send_cb_called = 0;
+  ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 4;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL);
+
+  ud.block_count = 2;
+  /* Sends request HEADERS + DATA[0] */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_DATA == ud.sent_frame_type);
+  /* data for DATA[1] is read from data_prd but it is not sent */
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN * 2);
+
+  nghttp2_submit_ping(session, NGHTTP2_FLAG_NONE, NULL);
+  ud.block_count = 2;
+  /* Sends DATA[1] + PING, PING is interleaved in DATA sequence */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_PING == ud.sent_frame_type);
+  /* data for DATA[2] is read from data_prd but it is not sent */
+  CU_ASSERT(ud.data_source_length == NGHTTP2_DATA_PAYLOADLEN);
+
+  ud.block_count = 2;
+  /* Sends DATA[2..3] */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  stream = nghttp2_session_get_stream(session, 1);
+  CU_ASSERT(stream->shut_flags & NGHTTP2_SHUT_WR);
+
+  nghttp2_session_del(session);
+}
+
+static void check_session_recv_data_with_padding(nghttp2_bufs *bufs,
+                                                 size_t datalen) {
+  nghttp2_session *session;
+  my_user_data ud;
+  nghttp2_session_callbacks callbacks;
+  uint8_t *in;
+  size_t inlen;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+  callbacks.on_data_chunk_recv_callback = on_data_chunk_recv_callback;
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
+                              &pri_spec_default, NGHTTP2_STREAM_OPENING, NULL);
+
+  inlen = nghttp2_bufs_remove(bufs, &in);
+
+  ud.frame_recv_cb_called = 0;
+  ud.data_chunk_len = 0;
+
+  CU_ASSERT((ssize_t)inlen == nghttp2_session_mem_recv(session, in, inlen));
+
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+  CU_ASSERT(datalen == ud.data_chunk_len);
+
+  free(in);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_pack_data_with_padding(void) {
+  nghttp2_session *session;
+  my_user_data ud;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_data_provider data_prd;
+  nghttp2_frame *frame;
+  size_t datalen = 55;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = block_count_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  callbacks.select_padding_callback = select_padding_callback;
+
+  data_prd.read_callback = fixed_length_data_source_read_callback;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+
+  ud.padlen = 63;
+
+  nghttp2_submit_request(session, NULL, NULL, 0, &data_prd, NULL);
+  ud.block_count = 1;
+  ud.data_source_length = datalen;
+  /* Sends HEADERS */
+  CU_ASSERT(0 == nghttp2_session_send(session));
+  CU_ASSERT(NGHTTP2_HEADERS == ud.sent_frame_type);
+
+  frame = &session->aob.item->frame;
+
+  CU_ASSERT(ud.padlen == frame->data.padlen);
+  CU_ASSERT(frame->hd.flags & NGHTTP2_FLAG_PADDED);
+
+  /* Check reception of this DATA frame */
+  check_session_recv_data_with_padding(&session->aob.framebufs, datalen);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_pack_headers_with_padding(void) {
+  nghttp2_session *session, *sv_session;
+  accumulator acc;
+  my_user_data ud;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_nv nv = MAKE_NV(":path", "/");
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = accumulator_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  callbacks.select_padding_callback = select_padding_callback;
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
+
+  acc.length = 0;
+  ud.acc = &acc;
+
+  nghttp2_session_client_new(&session, &callbacks, &ud);
+  nghttp2_session_server_new(&sv_session, &callbacks, &ud);
+
+  ud.padlen = 163;
+
+  CU_ASSERT(1 == nghttp2_submit_request(session, NULL, &nv, 1, NULL, NULL));
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(acc.length < NGHTTP2_MAX_PAYLOADLEN);
+  ud.frame_recv_cb_called = 0;
+  CU_ASSERT((ssize_t)acc.length ==
+            nghttp2_session_mem_recv(sv_session, acc.buf, acc.length));
+  CU_ASSERT(1 == ud.frame_recv_cb_called);
+  CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(sv_session));
+
+  nghttp2_session_del(sv_session);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_pack_settings_payload(void) {
+  nghttp2_settings_entry iv[2];
+  uint8_t buf[64];
+  ssize_t len;
+  nghttp2_settings_entry *resiv;
+  size_t resniv;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
+  iv[0].value = 1023;
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = 4095;
+
+  len = nghttp2_pack_settings_payload(buf, sizeof(buf), iv, 2);
+  CU_ASSERT(2 * NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH == len);
+  CU_ASSERT(0 == nghttp2_frame_unpack_settings_payload2(&resiv, &resniv, buf,
+                                                        len, mem));
+  CU_ASSERT(2 == resniv);
+  CU_ASSERT(NGHTTP2_SETTINGS_HEADER_TABLE_SIZE == resiv[0].settings_id);
+  CU_ASSERT(1023 == resiv[0].value);
+  CU_ASSERT(NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE == resiv[1].settings_id);
+  CU_ASSERT(4095 == resiv[1].value);
+
+  free(resiv);
+
+  len = nghttp2_pack_settings_payload(buf, 9 /* too small */, iv, 2);
+  CU_ASSERT(NGHTTP2_ERR_INSUFF_BUFSIZE == len);
+}
+
+#define check_stream_dep_sib(STREAM, DEP_PREV, DEP_NEXT, SIB_PREV, SIB_NEXT)   \
+  do {                                                                         \
+    CU_ASSERT(DEP_PREV == STREAM->dep_prev);                                   \
+    CU_ASSERT(DEP_NEXT == STREAM->dep_next);                                   \
+    CU_ASSERT(SIB_PREV == STREAM->sib_prev);                                   \
+    CU_ASSERT(SIB_NEXT == STREAM->sib_next);                                   \
+  } while (0)
+
+/* nghttp2_stream_dep_add() and its families functions should be
+   tested in nghttp2_stream_test.c, but it is easier to use
+   nghttp2_session_open_stream().  Therefore, we test them here. */
+void test_nghttp2_session_stream_dep_add(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d, *e;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+
+  c = open_stream_with_dep(session, 5, a);
+  b = open_stream_with_dep(session, 3, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * b--c
+   *    |
+   *    d
+   */
+
+  CU_ASSERT(4 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, b, NULL, NULL);
+  check_stream_dep_sib(b, a, NULL, NULL, c);
+  check_stream_dep_sib(c, NULL, d, b, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  CU_ASSERT(4 == session->roots.num_streams);
+  CU_ASSERT(a == session->roots.head);
+  CU_ASSERT(NULL == a->root_next);
+
+  e = open_stream_with_dep_excl(session, 9, a);
+
+  /* a
+   * |
+   * e
+   * |
+   * b--c
+   *    |
+   *    d
+   */
+
+  CU_ASSERT(5 == a->num_substreams);
+  CU_ASSERT(4 == e->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == e->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, e, NULL, NULL);
+  check_stream_dep_sib(e, a, b, NULL, NULL);
+  check_stream_dep_sib(b, e, NULL, NULL, c);
+  check_stream_dep_sib(c, NULL, d, b, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  CU_ASSERT(5 == session->roots.num_streams);
+  CU_ASSERT(a == session->roots.head);
+  CU_ASSERT(NULL == a->root_next);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_dep_remove(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d, *e, *f;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  /* Remove root */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * c--b
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_remove(a);
+
+  /* becomes:
+   * b    c
+   *      |
+   *      d
+   */
+
+  CU_ASSERT(1 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(0 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, NULL, NULL, NULL);
+  check_stream_dep_sib(b, NULL, NULL, NULL, NULL);
+  check_stream_dep_sib(c, NULL, d, NULL, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  CU_ASSERT(3 == session->roots.num_streams);
+  CU_ASSERT(b == session->roots.head);
+  CU_ASSERT(c == b->root_next);
+  CU_ASSERT(NULL == c->root_next);
+
+  nghttp2_session_del(session);
+
+  /* Remove left most stream */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * c--b
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_remove(b);
+
+  /* becomes:
+   * a
+   * |
+   * c
+   * |
+   * d
+   */
+
+  CU_ASSERT(3 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, c, NULL, NULL);
+  check_stream_dep_sib(b, NULL, NULL, NULL, NULL);
+  check_stream_dep_sib(c, a, d, NULL, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  CU_ASSERT(3 == session->roots.num_streams);
+  CU_ASSERT(a == session->roots.head);
+  CU_ASSERT(NULL == a->root_next);
+
+  nghttp2_session_del(session);
+
+  /* Remove right most stream */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * c--b
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_remove(c);
+
+  /* becomes:
+   * a
+   * |
+   * d--b
+   */
+
+  CU_ASSERT(3 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(1 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(0 == c->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, d, NULL, NULL);
+  check_stream_dep_sib(b, NULL, NULL, d, NULL);
+  check_stream_dep_sib(c, NULL, NULL, NULL, NULL);
+  check_stream_dep_sib(d, a, NULL, NULL, b);
+
+  nghttp2_session_del(session);
+
+  /* Remove middle stream */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, a);
+  e = open_stream_with_dep(session, 9, c);
+  f = open_stream_with_dep(session, 11, c);
+
+  /* a
+   * |
+   * d--c--b
+   *    |
+   *    f--e
+   */
+
+  CU_ASSERT(6 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(3 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+  CU_ASSERT(1 == e->num_substreams);
+  CU_ASSERT(1 == f->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(0 == e->sum_dep_weight);
+  CU_ASSERT(0 == f->sum_dep_weight);
+
+  nghttp2_stream_dep_remove(c);
+
+  /* becomes:
+   * a
+   * |
+   * d--f--e--b
+   */
+
+  CU_ASSERT(5 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(1 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+  CU_ASSERT(1 == e->num_substreams);
+  CU_ASSERT(1 == f->num_substreams);
+
+  /* c's weight 16 is distributed evenly to e and f.  Each weight of e
+     and f becomes 8. */
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 + 8 * 2 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(0 == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(0 == e->sum_dep_weight);
+  CU_ASSERT(0 == f->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, d, NULL, NULL);
+  check_stream_dep_sib(b, NULL, NULL, e, NULL);
+  check_stream_dep_sib(c, NULL, NULL, NULL, NULL);
+  check_stream_dep_sib(e, NULL, NULL, f, b);
+  check_stream_dep_sib(f, NULL, NULL, d, e);
+  check_stream_dep_sib(d, a, NULL, NULL, f);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_dep_add_subtree(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d, *e, *f;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  /* dep_stream has dep_next */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  e = open_stream(session, 9);
+  f = open_stream_with_dep(session, 11, e);
+
+  /* a         e
+   * |         |
+   * c--b      f
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_add_subtree(a, e, session);
+
+  /* becomes
+   * a
+   * |
+   * e--c--b
+   * |  |
+   * f  d
+   */
+
+  CU_ASSERT(6 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+  CU_ASSERT(2 == e->num_substreams);
+  CU_ASSERT(1 == f->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == e->sum_dep_weight);
+  CU_ASSERT(0 == f->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, e, NULL, NULL);
+  check_stream_dep_sib(b, NULL, NULL, c, NULL);
+  check_stream_dep_sib(c, NULL, d, e, b);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+  check_stream_dep_sib(e, a, f, NULL, c);
+  check_stream_dep_sib(f, e, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+
+  /* dep_stream has dep_next and now we insert subtree */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  e = open_stream(session, 9);
+  f = open_stream_with_dep(session, 11, e);
+
+  /* a         e
+   * |         |
+   * c--b      f
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_insert_subtree(a, e, session);
+
+  /* becomes
+   * a
+   * |
+   * e
+   * |
+   * f--c--b
+   *    |
+   *    d
+   */
+
+  CU_ASSERT(6 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+  CU_ASSERT(5 == e->num_substreams);
+  CU_ASSERT(1 == f->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 3 == e->sum_dep_weight);
+  CU_ASSERT(0 == f->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, e, NULL, NULL);
+  check_stream_dep_sib(e, a, f, NULL, NULL);
+  check_stream_dep_sib(f, e, NULL, NULL, c);
+  check_stream_dep_sib(b, NULL, NULL, c, NULL);
+  check_stream_dep_sib(c, NULL, d, f, b);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_dep_remove_subtree(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d, *e;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  /* Remove left most stream */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * c--b
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_remove_subtree(c);
+
+  /* becomes
+   * a  c
+   * |  |
+   * b  d
+   */
+
+  CU_ASSERT(2 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, b, NULL, NULL);
+  check_stream_dep_sib(b, a, NULL, NULL, NULL);
+  check_stream_dep_sib(c, NULL, d, NULL, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+
+  /* Remove right most stream */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * c--b
+   * |
+   * d
+   */
+
+  nghttp2_stream_dep_remove_subtree(b);
+
+  /* becomes
+   * a  b
+   * |
+   * c
+   * |
+   * d
+   */
+
+  CU_ASSERT(3 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, c, NULL, NULL);
+  check_stream_dep_sib(c, a, d, NULL, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+  check_stream_dep_sib(b, NULL, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+
+  /* Remove middle stream */
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  e = open_stream_with_dep(session, 9, a);
+  c = open_stream_with_dep(session, 5, a);
+  b = open_stream_with_dep(session, 3, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * b--c--e
+   *    |
+   *    d
+   */
+
+  nghttp2_stream_dep_remove_subtree(c);
+
+  /* becomes
+   * a     c
+   * |     |
+   * b--e  d
+   */
+
+  CU_ASSERT(3 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+  CU_ASSERT(1 == e->num_substreams);
+  CU_ASSERT(2 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(0 == e->sum_dep_weight);
+
+  check_stream_dep_sib(a, NULL, b, NULL, NULL);
+  check_stream_dep_sib(b, a, NULL, NULL, e);
+  check_stream_dep_sib(e, NULL, NULL, b, NULL);
+  check_stream_dep_sib(c, NULL, d, NULL, NULL);
+  check_stream_dep_sib(d, c, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+
+  c = open_stream(session, 5);
+
+  /* a     c
+   * |
+   * b
+   */
+
+  nghttp2_stream_dep_remove_subtree(c);
+  CU_ASSERT(0 ==
+            nghttp2_stream_dep_all_your_stream_are_belong_to_us(c, session));
+
+  /*
+   * c
+   * |
+   * a
+   * |
+   * b
+   */
+
+  CU_ASSERT(3 == c->num_substreams);
+  CU_ASSERT(2 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == c->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+
+  check_stream_dep_sib(c, NULL, a, NULL, NULL);
+  check_stream_dep_sib(a, c, b, NULL, NULL);
+  check_stream_dep_sib(b, a, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+
+  b = open_stream(session, 3);
+
+  c = open_stream(session, 5);
+
+  /*
+   * a  b   c
+   */
+
+  nghttp2_stream_dep_remove_subtree(c);
+  CU_ASSERT(0 ==
+            nghttp2_stream_dep_all_your_stream_are_belong_to_us(c, session));
+
+  /*
+   * c
+   * |
+   * b--a
+   */
+
+  CU_ASSERT(3 == c->num_substreams);
+  CU_ASSERT(1 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+  CU_ASSERT(0 == a->sum_dep_weight);
+
+  check_stream_dep_sib(c, NULL, b, NULL, NULL);
+  check_stream_dep_sib(b, c, NULL, NULL, a);
+  check_stream_dep_sib(a, NULL, NULL, b, NULL);
+
+  nghttp2_session_del(session);
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+
+  c = open_stream(session, 5);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a     c
+   * |     |
+   * b     d
+   */
+
+  nghttp2_stream_dep_remove_subtree(c);
+  CU_ASSERT(0 ==
+            nghttp2_stream_dep_all_your_stream_are_belong_to_us(c, session));
+
+  /*
+   * c
+   * |
+   * a--d
+   * |
+   * b
+   */
+
+  CU_ASSERT(4 == c->num_substreams);
+  CU_ASSERT(1 == d->num_substreams);
+  CU_ASSERT(2 == a->num_substreams);
+  CU_ASSERT(1 == b->num_substreams);
+
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT * 2 == c->sum_dep_weight);
+  CU_ASSERT(0 == d->sum_dep_weight);
+  CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == a->sum_dep_weight);
+  CU_ASSERT(0 == b->sum_dep_weight);
+
+  check_stream_dep_sib(c, NULL, a, NULL, NULL);
+  check_stream_dep_sib(d, NULL, NULL, a, NULL);
+  check_stream_dep_sib(a, c, b, NULL, d);
+  check_stream_dep_sib(b, a, NULL, NULL, NULL);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_attach_item(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d;
+  nghttp2_outbound_item *da, *db, *dc, *dd;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  /* a
+   * |
+   * c--b
+   * |
+   * d
+   */
+
+  db = create_data_ob_item();
+
+  nghttp2_stream_attach_item(b, db, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+
+  CU_ASSERT(16 == b->effective_weight);
+
+  CU_ASSERT(16 == a->sum_norest_weight);
+
+  CU_ASSERT(1 == db->queued);
+
+  dc = create_data_ob_item();
+
+  nghttp2_stream_attach_item(c, dc, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+
+  CU_ASSERT(16 * 16 / 32 == b->effective_weight);
+  CU_ASSERT(16 * 16 / 32 == c->effective_weight);
+
+  CU_ASSERT(32 == a->sum_norest_weight);
+
+  CU_ASSERT(1 == dc->queued);
+
+  da = create_data_ob_item();
+
+  nghttp2_stream_attach_item(a, da, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+
+  CU_ASSERT(16 == a->effective_weight);
+
+  CU_ASSERT(1 == da->queued);
+
+  nghttp2_stream_detach_item(a, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+
+  CU_ASSERT(16 * 16 / 32 == b->effective_weight);
+  CU_ASSERT(16 * 16 / 32 == c->effective_weight);
+
+  dd = create_data_ob_item();
+
+  nghttp2_stream_attach_item(d, dd, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
+
+  CU_ASSERT(16 * 16 / 32 == b->effective_weight);
+  CU_ASSERT(16 * 16 / 32 == c->effective_weight);
+
+  CU_ASSERT(0 == dd->queued);
+
+  nghttp2_stream_detach_item(c, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
+
+  CU_ASSERT(16 * 16 / 16 == b->effective_weight);
+
+  CU_ASSERT(0 == dd->queued);
+
+  nghttp2_stream_detach_item(b, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == d->dpri);
+
+  CU_ASSERT(16 * 16 / 16 == d->effective_weight);
+
+  CU_ASSERT(1 == dd->queued);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_stream_attach_item_subtree(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a, *b, *c, *d, *e, *f;
+  nghttp2_outbound_item *db, *dd, *de;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  b = open_stream_with_dep(session, 3, a);
+  c = open_stream_with_dep(session, 5, a);
+  d = open_stream_with_dep(session, 7, c);
+
+  e = open_stream(session, 9);
+  f = open_stream_with_dep(session, 11, e);
+  /*
+   * a        e
+   * |        |
+   * c--b     f
+   * |
+   * d
+   */
+
+  de = create_data_ob_item();
+
+  nghttp2_stream_attach_item(e, de, session);
+
+  db = create_data_ob_item();
+
+  nghttp2_stream_attach_item(b, db, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  CU_ASSERT(16 == b->effective_weight);
+  CU_ASSERT(16 == e->effective_weight);
+
+  /* Insert subtree e under a */
+
+  nghttp2_stream_dep_remove_subtree(e);
+  nghttp2_stream_dep_insert_subtree(a, e, session);
+
+  /*
+   * a
+   * |
+   * e
+   * |
+   * f--c--b
+   *    |
+   *    d
+   */
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  CU_ASSERT(16 == e->effective_weight);
+
+  /* Remove subtree b */
+
+  nghttp2_stream_dep_remove_subtree(b);
+
+  nghttp2_stream_dep_make_root(b, session);
+
+  /*
+   * a       b
+   * |
+   * e
+   * |
+   * f--c
+   *    |
+   *    d
+   */
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  CU_ASSERT(16 == b->effective_weight);
+  CU_ASSERT(16 == e->effective_weight);
+
+  /* Remove subtree a */
+
+  nghttp2_stream_dep_remove_subtree(a);
+
+  nghttp2_stream_dep_make_root(a, session);
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  /* Remove subtree c */
+
+  nghttp2_stream_dep_remove_subtree(c);
+
+  nghttp2_stream_dep_make_root(c, session);
+
+  /*
+   * a       b     c
+   * |             |
+   * e             d
+   * |
+   * f
+   */
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  dd = create_data_ob_item();
+
+  nghttp2_stream_attach_item(d, dd, session);
+
+  /* Add subtree c to a */
+
+  nghttp2_stream_dep_remove_subtree(c);
+  nghttp2_stream_dep_add_subtree(a, c, session);
+
+  /*
+   * a       b
+   * |
+   * c--e
+   * |  |
+   * d  f
+   */
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  CU_ASSERT(16 == b->effective_weight);
+  CU_ASSERT(16 * 16 / 16 == e->effective_weight);
+
+  CU_ASSERT(32 == a->sum_norest_weight);
+  CU_ASSERT(16 == c->sum_norest_weight);
+
+  /* Insert b under a */
+
+  nghttp2_stream_dep_remove_subtree(b);
+  nghttp2_stream_dep_insert_subtree(a, b, session);
+
+  /*
+   * a
+   * |
+   * b
+   * |
+   * e--c
+   * |  |
+   * f  d
+   */
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  CU_ASSERT(16 == b->effective_weight);
+
+  CU_ASSERT(16 == a->sum_norest_weight);
+  CU_ASSERT(0 == b->sum_norest_weight);
+
+  /* Remove subtree b */
+
+  nghttp2_stream_dep_remove_subtree(b);
+  nghttp2_stream_dep_make_root(b, session);
+
+  /*
+   * b       a
+   * |
+   * e--c
+   * |  |
+   * f  d
+   */
+
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == a->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_TOP == b->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == c->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == d->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_REST == e->dpri);
+  CU_ASSERT(NGHTTP2_STREAM_DPRI_NO_ITEM == f->dpri);
+
+  CU_ASSERT(0 == a->sum_norest_weight);
+  CU_ASSERT(0 == b->sum_norest_weight);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_keep_closed_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const size_t max_concurrent_streams = 5;
+  nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+                               max_concurrent_streams};
+  size_t i;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+  for (i = 0; i < max_concurrent_streams; ++i) {
+    open_stream(session, (int)i * 2 + 1);
+  }
+
+  CU_ASSERT(0 == session->num_closed_streams);
+
+  nghttp2_session_close_stream(session, 1, NGHTTP2_NO_ERROR);
+
+  CU_ASSERT(1 == session->num_closed_streams);
+  CU_ASSERT(1 == session->closed_stream_tail->stream_id);
+  CU_ASSERT(session->closed_stream_tail == session->closed_stream_head);
+
+  nghttp2_session_close_stream(session, 5, NGHTTP2_NO_ERROR);
+
+  CU_ASSERT(2 == session->num_closed_streams);
+  CU_ASSERT(5 == session->closed_stream_tail->stream_id);
+  CU_ASSERT(1 == session->closed_stream_head->stream_id);
+  CU_ASSERT(session->closed_stream_head ==
+            session->closed_stream_tail->closed_prev);
+  CU_ASSERT(NULL == session->closed_stream_tail->closed_next);
+  CU_ASSERT(session->closed_stream_tail ==
+            session->closed_stream_head->closed_next);
+  CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
+
+  open_stream(session, 11);
+
+  CU_ASSERT(1 == session->num_closed_streams);
+  CU_ASSERT(5 == session->closed_stream_tail->stream_id);
+  CU_ASSERT(session->closed_stream_tail == session->closed_stream_head);
+  CU_ASSERT(NULL == session->closed_stream_head->closed_prev);
+  CU_ASSERT(NULL == session->closed_stream_head->closed_next);
+
+  open_stream(session, 13);
+
+  CU_ASSERT(0 == session->num_closed_streams);
+  CU_ASSERT(NULL == session->closed_stream_tail);
+  CU_ASSERT(NULL == session->closed_stream_head);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_keep_idle_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  const size_t max_concurrent_streams = 1;
+  nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+                               max_concurrent_streams};
+  int i;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
+
+  /* We at least allow 2 idle streams even if max concurrent streams
+     is very low. */
+  for (i = 0; i < 2; ++i) {
+    nghttp2_session_open_stream(session, i * 2 + 1, NGHTTP2_STREAM_FLAG_NONE,
+                                &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
+  }
+
+  CU_ASSERT(2 == session->num_idle_streams);
+
+  CU_ASSERT(1 == session->idle_stream_head->stream_id);
+  CU_ASSERT(3 == session->idle_stream_tail->stream_id);
+
+  nghttp2_session_open_stream(session, 5, NGHTTP2_FLAG_NONE, &pri_spec_default,
+                              NGHTTP2_STREAM_IDLE, NULL);
+
+  CU_ASSERT(2 == session->num_idle_streams);
+
+  CU_ASSERT(3 == session->idle_stream_head->stream_id);
+  CU_ASSERT(5 == session->idle_stream_tail->stream_id);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_detach_idle_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  int i;
+  nghttp2_stream *stream;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  for (i = 1; i <= 3; ++i) {
+    nghttp2_session_open_stream(session, i, NGHTTP2_STREAM_FLAG_NONE,
+                                &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
+  }
+
+  CU_ASSERT(3 == session->num_idle_streams);
+
+  /* Detach middle stream */
+  stream = nghttp2_session_get_stream_raw(session, 2);
+
+  CU_ASSERT(session->idle_stream_head == stream->closed_prev);
+  CU_ASSERT(session->idle_stream_tail == stream->closed_next);
+  CU_ASSERT(stream == session->idle_stream_head->closed_next);
+  CU_ASSERT(stream == session->idle_stream_tail->closed_prev);
+
+  nghttp2_session_detach_idle_stream(session, stream);
+
+  CU_ASSERT(2 == session->num_idle_streams);
+
+  CU_ASSERT(NULL == stream->closed_prev);
+  CU_ASSERT(NULL == stream->closed_next);
+
+  CU_ASSERT(session->idle_stream_head ==
+            session->idle_stream_tail->closed_prev);
+  CU_ASSERT(session->idle_stream_tail ==
+            session->idle_stream_head->closed_next);
+
+  /* Detach head stream */
+  stream = session->idle_stream_head;
+
+  nghttp2_session_detach_idle_stream(session, stream);
+
+  CU_ASSERT(1 == session->num_idle_streams);
+
+  CU_ASSERT(session->idle_stream_head == session->idle_stream_tail);
+  CU_ASSERT(NULL == session->idle_stream_head->closed_prev);
+  CU_ASSERT(NULL == session->idle_stream_head->closed_next);
+
+  /* Detach last stream */
+
+  stream = session->idle_stream_head;
+
+  nghttp2_session_detach_idle_stream(session, stream);
+
+  CU_ASSERT(0 == session->num_idle_streams);
+
+  CU_ASSERT(NULL == session->idle_stream_head);
+  CU_ASSERT(NULL == session->idle_stream_tail);
+
+  for (i = 4; i <= 5; ++i) {
+    nghttp2_session_open_stream(session, i, NGHTTP2_STREAM_FLAG_NONE,
+                                &pri_spec_default, NGHTTP2_STREAM_IDLE, NULL);
+  }
+
+  CU_ASSERT(2 == session->num_idle_streams);
+
+  /* Detach tail stream */
+
+  stream = session->idle_stream_tail;
+
+  nghttp2_session_detach_idle_stream(session, stream);
+
+  CU_ASSERT(1 == session->num_idle_streams);
+
+  CU_ASSERT(session->idle_stream_head == session->idle_stream_tail);
+  CU_ASSERT(NULL == session->idle_stream_head->closed_prev);
+  CU_ASSERT(NULL == session->idle_stream_head->closed_next);
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_large_dep_tree(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  size_t i;
+  nghttp2_stream *dep_stream = NULL;
+  nghttp2_stream *root_stream;
+  int32_t stream_id;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = null_send_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  stream_id = 1;
+  for (i = 0; i < NGHTTP2_MAX_DEP_TREE_LENGTH; ++i) {
+    dep_stream = open_stream_with_dep(session, stream_id, dep_stream);
+    stream_id += 2;
+  }
+
+  root_stream = nghttp2_session_get_stream(session, 1);
+
+  /* Check that last dep_stream must be part of tree */
+  CU_ASSERT(nghttp2_stream_dep_subtree_find(root_stream, dep_stream));
+
+  dep_stream = open_stream_with_dep(session, stream_id, dep_stream);
+
+  /* We exceeded NGHTTP2_MAX_DEP_TREE_LENGTH limit.  dep_stream is now
+     root node and has no descendants. */
+  CU_ASSERT(!nghttp2_stream_dep_subtree_find(root_stream, dep_stream));
+  CU_ASSERT(nghttp2_stream_in_dep_tree(dep_stream));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_graceful_shutdown(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.send_callback = null_send_callback;
+  callbacks.on_frame_send_callback = on_frame_send_callback;
+  callbacks.on_stream_close_callback = on_stream_close_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  open_stream(session, 301);
+  open_stream(session, 302);
+  open_stream(session, 309);
+  open_stream(session, 311);
+  open_stream(session, 319);
+
+  CU_ASSERT(0 == nghttp2_submit_shutdown_notice(session));
+
+  ud.frame_send_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT((1u << 31) - 1 == session->local_last_stream_id);
+
+  CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_FLAG_NONE, 311,
+                                       NGHTTP2_NO_ERROR, NULL, 0));
+
+  ud.frame_send_cb_called = 0;
+  ud.stream_close_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(311 == session->local_last_stream_id);
+  CU_ASSERT(1 == ud.stream_close_cb_called);
+
+  CU_ASSERT(0 ==
+            nghttp2_session_terminate_session2(session, 301, NGHTTP2_NO_ERROR));
+
+  ud.frame_send_cb_called = 0;
+  ud.stream_close_cb_called = 0;
+
+  CU_ASSERT(0 == nghttp2_session_send(session));
+
+  CU_ASSERT(1 == ud.frame_send_cb_called);
+  CU_ASSERT(301 == session->local_last_stream_id);
+  CU_ASSERT(2 == ud.stream_close_cb_called);
+
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 301));
+  CU_ASSERT(NULL != nghttp2_session_get_stream(session, 302));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 309));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 311));
+  CU_ASSERT(NULL == nghttp2_session_get_stream(session, 319));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_on_header_temporal_failure(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  my_user_data ud;
+  nghttp2_bufs bufs;
+  nghttp2_buf *buf;
+  nghttp2_hd_deflater deflater;
+  nghttp2_nv nv[] = {MAKE_NV("alpha", "bravo"), MAKE_NV("charlie", "delta")};
+  nghttp2_nv *nva;
+  size_t hdpos;
+  ssize_t rv;
+  nghttp2_frame frame;
+  nghttp2_frame_hd hd;
+  nghttp2_outbound_item *item;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+  memset(&callbacks, 0, sizeof(callbacks));
+  callbacks.on_header_callback = temporal_failure_on_header_callback;
+
+  nghttp2_session_server_new(&session, &callbacks, &ud);
+
+  frame_pack_bufs_init(&bufs);
+
+  nghttp2_hd_deflate_init(&deflater, mem);
+
+  nghttp2_nv_array_copy(&nva, nv, 1, mem);
+
+  nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_STREAM, 1,
+                             NGHTTP2_HCAT_REQUEST, NULL, nva, 1);
+  nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
+  nghttp2_frame_headers_free(&frame.headers, mem);
+
+  /* We are going to create CONTINUATION.  First serialize header
+     block, and then frame header. */
+  hdpos = nghttp2_bufs_len(&bufs);
+
+  buf = &bufs.head->buf;
+  buf->last += NGHTTP2_FRAME_HDLEN;
+
+  nghttp2_hd_deflate_hd_bufs(&deflater, &bufs, &nv[1], 1);
+
+  nghttp2_frame_hd_init(&hd,
+                        nghttp2_bufs_len(&bufs) - hdpos - NGHTTP2_FRAME_HDLEN,
+                        NGHTTP2_CONTINUATION, NGHTTP2_FLAG_END_HEADERS, 1);
+
+  nghttp2_frame_pack_frame_hd(&buf->pos[hdpos], &hd);
+
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_bufs_len(&bufs));
+
+  CU_ASSERT(rv == nghttp2_bufs_len(&bufs));
+
+  item = nghttp2_session_get_next_ob_item(session);
+
+  CU_ASSERT(NGHTTP2_RST_STREAM == item->frame.hd.type);
+
+  /* Make sure no header decompression error occurred */
+  CU_ASSERT(NGHTTP2_GOAWAY_NONE == session->goaway_flags);
+
+  nghttp2_bufs_free(&bufs);
+
+  nghttp2_hd_deflate_free(&deflater);
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_recv_client_preface(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_option *option;
+  ssize_t rv;
+  nghttp2_frame ping_frame;
+  uint8_t buf[16];
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  nghttp2_option_new(&option);
+  nghttp2_option_set_recv_client_preface(option, 1);
+
+  /* Check success case */
+  nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+  CU_ASSERT(session->opt_flags & NGHTTP2_OPTMASK_RECV_CLIENT_PREFACE);
+
+  rv = nghttp2_session_mem_recv(
+      session, (const uint8_t *)NGHTTP2_CLIENT_CONNECTION_PREFACE,
+      NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+
+  CU_ASSERT(rv == NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN);
+  CU_ASSERT(NGHTTP2_IB_READ_FIRST_SETTINGS == session->iframe.state);
+
+  /* Receiving PING is error */
+  nghttp2_frame_ping_init(&ping_frame.ping, NGHTTP2_FLAG_NONE, NULL);
+
+  nghttp2_frame_pack_frame_hd(buf, &ping_frame.ping.hd);
+
+  rv = nghttp2_session_mem_recv(session, buf, NGHTTP2_FRAME_HDLEN);
+  CU_ASSERT(NGHTTP2_FRAME_HDLEN == rv);
+  CU_ASSERT(NGHTTP2_IB_IGN_PAYLOAD == session->iframe.state);
+  CU_ASSERT(8 == session->iframe.payloadleft);
+
+  nghttp2_frame_ping_free(&ping_frame.ping);
+
+  nghttp2_session_del(session);
+
+  /* Check bad case */
+  nghttp2_session_server_new2(&session, &callbacks, NULL, option);
+
+  /* Feed preface with one byte less */
+  rv = nghttp2_session_mem_recv(
+      session, (const uint8_t *)NGHTTP2_CLIENT_CONNECTION_PREFACE,
+      NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - 1);
+
+  CU_ASSERT(rv == NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN - 1);
+  CU_ASSERT(NGHTTP2_IB_READ_CLIENT_PREFACE == session->iframe.state);
+  CU_ASSERT(1 == session->iframe.payloadleft);
+
+  rv = nghttp2_session_mem_recv(session, (const uint8_t *)"\0", 1);
+
+  CU_ASSERT(NGHTTP2_ERR_BAD_PREFACE == rv);
+
+  nghttp2_session_del(session);
+
+  nghttp2_option_del(option);
+}
+
+void test_nghttp2_session_delete_data_item(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *a;
+  nghttp2_data_provider prd;
+
+  memset(&callbacks, 0, sizeof(callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  a = open_stream(session, 1);
+  open_stream_with_dep(session, 3, a);
+
+  /* We don't care about these members, since we won't send data */
+  prd.source.ptr = NULL;
+  prd.read_callback = fail_data_source_read_callback;
+
+  /* This data item will be marked as TOP */
+  CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 1, &prd));
+  /* This data item will be marked as REST */
+  CU_ASSERT(0 == nghttp2_submit_data(session, NGHTTP2_FLAG_NONE, 3, &prd));
+
+  nghttp2_session_del(session);
+}
+
+void test_nghttp2_session_open_idle_stream(void) {
+  nghttp2_session *session;
+  nghttp2_session_callbacks callbacks;
+  nghttp2_stream *stream;
+  nghttp2_stream *opened_stream;
+  nghttp2_priority_spec pri_spec;
+  nghttp2_frame frame;
+
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
+
+  nghttp2_session_server_new(&session, &callbacks, NULL);
+
+  nghttp2_priority_spec_init(&pri_spec, 0, 3, 0);
+
+  nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
+
+  CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame));
+
+  stream = nghttp2_session_get_stream_raw(session, 1);
+
+  CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state);
+  CU_ASSERT(NULL == stream->closed_prev);
+  CU_ASSERT(NULL == stream->closed_next);
+  CU_ASSERT(1 == session->num_idle_streams);
+  CU_ASSERT(session->idle_stream_head == stream);
+  CU_ASSERT(session->idle_stream_tail == stream);
+
+  opened_stream = nghttp2_session_open_stream(
+      session, 1, NGHTTP2_STREAM_FLAG_NONE, &pri_spec_default,
+      NGHTTP2_STREAM_OPENING, NULL);
+
+  CU_ASSERT(stream == opened_stream);
+  CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
+  CU_ASSERT(0 == session->num_idle_streams);
+  CU_ASSERT(NULL == session->idle_stream_head);
+  CU_ASSERT(NULL == session->idle_stream_tail);
+
+  nghttp2_frame_priority_free(&frame.priority);
+
+  nghttp2_session_del(session);
+}
diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h
new file mode 100644 (file)
index 0000000..ee13d0e
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_SESSION_TEST_H
+#define NGHTTP2_SESSION_TEST_H
+
+void test_nghttp2_session_recv(void);
+void test_nghttp2_session_recv_invalid_stream_id(void);
+void test_nghttp2_session_recv_invalid_frame(void);
+void test_nghttp2_session_recv_eof(void);
+void test_nghttp2_session_recv_data(void);
+void test_nghttp2_session_recv_continuation(void);
+void test_nghttp2_session_recv_headers_with_priority(void);
+void test_nghttp2_session_recv_premature_headers(void);
+void test_nghttp2_session_recv_unknown_frame(void);
+void test_nghttp2_session_recv_unexpected_continuation(void);
+void test_nghttp2_session_recv_settings_header_table_size(void);
+void test_nghttp2_session_recv_too_large_frame_length(void);
+void test_nghttp2_session_continue(void);
+void test_nghttp2_session_add_frame(void);
+void test_nghttp2_session_on_request_headers_received(void);
+void test_nghttp2_session_on_response_headers_received(void);
+void test_nghttp2_session_on_headers_received(void);
+void test_nghttp2_session_on_push_response_headers_received(void);
+void test_nghttp2_session_on_priority_received(void);
+void test_nghttp2_session_on_rst_stream_received(void);
+void test_nghttp2_session_on_settings_received(void);
+void test_nghttp2_session_on_push_promise_received(void);
+void test_nghttp2_session_on_ping_received(void);
+void test_nghttp2_session_on_goaway_received(void);
+void test_nghttp2_session_on_window_update_received(void);
+void test_nghttp2_session_on_data_received(void);
+void test_nghttp2_session_send_headers_start_stream(void);
+void test_nghttp2_session_send_headers_reply(void);
+void test_nghttp2_session_send_headers_frame_size_error(void);
+void test_nghttp2_session_send_headers_push_reply(void);
+void test_nghttp2_session_send_rst_stream(void);
+void test_nghttp2_session_send_push_promise(void);
+void test_nghttp2_session_is_my_stream_id(void);
+void test_nghttp2_session_upgrade(void);
+void test_nghttp2_session_reprioritize_stream(void);
+void test_nghttp2_session_reprioritize_stream_with_idle_stream_dep(void);
+void test_nghttp2_submit_data(void);
+void test_nghttp2_submit_data_read_length_too_large(void);
+void test_nghttp2_submit_data_read_length_smallest(void);
+void test_nghttp2_submit_data_twice(void);
+void test_nghttp2_submit_request_with_data(void);
+void test_nghttp2_submit_request_without_data(void);
+void test_nghttp2_submit_response_with_data(void);
+void test_nghttp2_submit_response_without_data(void);
+void test_nghttp2_submit_headers_start_stream(void);
+void test_nghttp2_submit_headers_reply(void);
+void test_nghttp2_submit_headers_push_reply(void);
+void test_nghttp2_submit_headers(void);
+void test_nghttp2_submit_headers_continuation(void);
+void test_nghttp2_submit_priority(void);
+void test_nghttp2_submit_settings(void);
+void test_nghttp2_submit_settings_update_local_window_size(void);
+void test_nghttp2_submit_push_promise(void);
+void test_nghttp2_submit_window_update(void);
+void test_nghttp2_submit_window_update_local_window_size(void);
+void test_nghttp2_submit_shutdown_notice(void);
+void test_nghttp2_submit_invalid_nv(void);
+void test_nghttp2_session_open_stream(void);
+void test_nghttp2_session_open_stream_with_idle_stream_dep(void);
+void test_nghttp2_session_get_next_ob_item(void);
+void test_nghttp2_session_pop_next_ob_item(void);
+void test_nghttp2_session_reply_fail(void);
+void test_nghttp2_session_max_concurrent_streams(void);
+void test_nghttp2_session_stream_close_on_headers_push(void);
+void test_nghttp2_session_stop_data_with_rst_stream(void);
+void test_nghttp2_session_defer_data(void);
+void test_nghttp2_session_flow_control(void);
+void test_nghttp2_session_flow_control_data_recv(void);
+void test_nghttp2_session_flow_control_data_with_padding_recv(void);
+void test_nghttp2_session_data_read_temporal_failure(void);
+void test_nghttp2_session_on_stream_close(void);
+void test_nghttp2_session_on_ctrl_not_send(void);
+void test_nghttp2_session_get_outbound_queue_size(void);
+void test_nghttp2_session_get_effective_local_window_size(void);
+void test_nghttp2_session_set_option(void);
+void test_nghttp2_session_data_backoff_by_high_pri_frame(void);
+void test_nghttp2_session_pack_data_with_padding(void);
+void test_nghttp2_session_pack_headers_with_padding(void);
+void test_nghttp2_pack_settings_payload(void);
+void test_nghttp2_session_stream_dep_add(void);
+void test_nghttp2_session_stream_dep_remove(void);
+void test_nghttp2_session_stream_dep_add_subtree(void);
+void test_nghttp2_session_stream_dep_remove_subtree(void);
+void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void);
+void test_nghttp2_session_stream_attach_item(void);
+void test_nghttp2_session_stream_attach_item_subtree(void);
+void test_nghttp2_session_keep_closed_stream(void);
+void test_nghttp2_session_keep_idle_stream(void);
+void test_nghttp2_session_detach_idle_stream(void);
+void test_nghttp2_session_large_dep_tree(void);
+void test_nghttp2_session_graceful_shutdown(void);
+void test_nghttp2_session_on_header_temporal_failure(void);
+void test_nghttp2_session_recv_client_preface(void);
+void test_nghttp2_session_delete_data_item(void);
+void test_nghttp2_session_open_idle_stream(void);
+
+#endif /* NGHTTP2_SESSION_TEST_H */
diff --git a/tests/nghttp2_stream_test.c b/tests/nghttp2_stream_test.c
new file mode 100644 (file)
index 0000000..9b572d0
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_stream_test.h"
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_stream.h"
diff --git a/tests/nghttp2_stream_test.h b/tests/nghttp2_stream_test.h
new file mode 100644 (file)
index 0000000..508a8e1
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_STREAM_TEST_H
+#define NGHTTP2_STREAM_TEST_H
+
+#endif /* NGHTTP2_STREAM_TEST_H */
diff --git a/tests/nghttp2_test_helper.c b/tests/nghttp2_test_helper.c
new file mode 100644 (file)
index 0000000..ba14901
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include "nghttp2_test_helper.h"
+
+#include <assert.h>
+
+#include <CUnit/CUnit.h>
+
+#include "nghttp2_helper.h"
+#include "nghttp2_priority_spec.h"
+
+int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs) {
+  nghttp2_buf *buf;
+
+  /* Assuming we have required data in first buffer. We don't decode
+     header block so, we don't mind its space */
+  buf = &bufs->head->buf;
+  return unpack_frame(frame, buf->pos, nghttp2_buf_len(buf));
+}
+
+int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len) {
+  int rv = 0;
+  const uint8_t *payload = in + NGHTTP2_FRAME_HDLEN;
+  size_t payloadlen = len - NGHTTP2_FRAME_HDLEN;
+  size_t payloadoff;
+  nghttp2_mem *mem;
+
+  mem = nghttp2_mem_default();
+
+  nghttp2_frame_unpack_frame_hd(&frame->hd, in);
+  switch (frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    payloadoff = ((frame->hd.flags & NGHTTP2_FLAG_PADDED) > 0);
+    rv = nghttp2_frame_unpack_headers_payload(
+        &frame->headers, payload + payloadoff, payloadlen - payloadoff);
+    break;
+  case NGHTTP2_PRIORITY:
+    nghttp2_frame_unpack_priority_payload(&frame->priority, payload,
+                                          payloadlen);
+    break;
+  case NGHTTP2_RST_STREAM:
+    nghttp2_frame_unpack_rst_stream_payload(&frame->rst_stream, payload,
+                                            payloadlen);
+    break;
+  case NGHTTP2_SETTINGS:
+    rv = nghttp2_frame_unpack_settings_payload2(
+        &frame->settings.iv, &frame->settings.niv, payload, payloadlen, mem);
+    break;
+  case NGHTTP2_PUSH_PROMISE:
+    rv = nghttp2_frame_unpack_push_promise_payload(&frame->push_promise,
+                                                   payload, payloadlen);
+    break;
+  case NGHTTP2_PING:
+    nghttp2_frame_unpack_ping_payload(&frame->ping, payload, payloadlen);
+    break;
+  case NGHTTP2_GOAWAY:
+    nghttp2_frame_unpack_goaway_payload2(&frame->goaway, payload, payloadlen,
+                                         mem);
+    break;
+  case NGHTTP2_WINDOW_UPDATE:
+    nghttp2_frame_unpack_window_update_payload(&frame->window_update, payload,
+                                               payloadlen);
+    break;
+  default:
+    /* Must not be reachable */
+    assert(0);
+  }
+  return rv;
+}
+
+int strmemeq(const char *a, const uint8_t *b, size_t bn) {
+  const uint8_t *c;
+  if (!a || !b) {
+    return 0;
+  }
+  c = b + bn;
+  for (; *a && b != c && *a == *b; ++a, ++b)
+    ;
+  return !*a && b == c;
+}
+
+int nvnameeq(const char *a, nghttp2_nv *nv) {
+  return strmemeq(a, nv->name, nv->namelen);
+}
+
+int nvvalueeq(const char *a, nghttp2_nv *nv) {
+  return strmemeq(a, nv->value, nv->valuelen);
+}
+
+void nva_out_init(nva_out *out) {
+  memset(out->nva, 0, sizeof(out->nva));
+  out->nvlen = 0;
+}
+
+void nva_out_reset(nva_out *out) {
+  size_t i;
+  for (i = 0; i < out->nvlen; ++i) {
+    free(out->nva[i].name);
+    free(out->nva[i].value);
+  }
+  memset(out->nva, 0, sizeof(out->nva));
+  out->nvlen = 0;
+}
+
+void add_out(nva_out *out, nghttp2_nv *nv) {
+  nghttp2_nv *onv = &out->nva[out->nvlen];
+  if (nv->namelen) {
+    onv->name = malloc(nv->namelen);
+    memcpy(onv->name, nv->name, nv->namelen);
+  } else {
+    onv->name = NULL;
+  }
+  if (nv->valuelen) {
+    onv->value = malloc(nv->valuelen);
+    memcpy(onv->value, nv->value, nv->valuelen);
+  } else {
+    onv->value = NULL;
+  }
+  onv->namelen = nv->namelen;
+  onv->valuelen = nv->valuelen;
+
+  onv->flags = nv->flags;
+
+  ++out->nvlen;
+}
+
+ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out,
+                   nghttp2_bufs *bufs, size_t offset) {
+  ssize_t rv;
+  nghttp2_nv nv;
+  int inflate_flags;
+  nghttp2_buf_chain *ci;
+  nghttp2_buf *buf;
+  nghttp2_buf bp;
+  int final;
+  size_t processed;
+
+  processed = 0;
+
+  for (ci = bufs->head; ci; ci = ci->next) {
+    buf = &ci->buf;
+    final = nghttp2_buf_len(buf) == 0 || ci->next == NULL;
+    bp = *buf;
+
+    if (offset) {
+      ssize_t n;
+
+      n = nghttp2_min((ssize_t)offset, nghttp2_buf_len(&bp));
+      bp.pos += n;
+      offset -= n;
+    }
+
+    for (;;) {
+      inflate_flags = 0;
+      rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, bp.pos,
+                                 nghttp2_buf_len(&bp), final);
+
+      if (rv < 0) {
+        return rv;
+      }
+
+      bp.pos += rv;
+      processed += rv;
+
+      if (inflate_flags & NGHTTP2_HD_INFLATE_EMIT) {
+        if (out) {
+          add_out(out, &nv);
+        }
+      }
+      if (inflate_flags & NGHTTP2_HD_INFLATE_FINAL) {
+        break;
+      }
+    }
+  }
+
+  nghttp2_hd_inflate_end_headers(inflater);
+
+  return processed;
+}
+
+int frame_pack_bufs_init(nghttp2_bufs *bufs) {
+  /* 1 for Pad Length */
+  return nghttp2_bufs_init2(bufs, 4096, 16, NGHTTP2_FRAME_HDLEN + 1,
+                            nghttp2_mem_default());
+}
+
+void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size) {
+  /* 1 for Pad Length */
+  nghttp2_bufs_init2(bufs, chunk_size, 16, NGHTTP2_FRAME_HDLEN + 1,
+                     nghttp2_mem_default());
+}
+
+static nghttp2_stream *open_stream_with_all(nghttp2_session *session,
+                                            int32_t stream_id, int32_t weight,
+                                            uint8_t exclusive,
+                                            nghttp2_stream *dep_stream) {
+  nghttp2_priority_spec pri_spec;
+  int32_t dep_stream_id;
+
+  if (dep_stream) {
+    dep_stream_id = dep_stream->stream_id;
+  } else {
+    dep_stream_id = 0;
+  }
+
+  nghttp2_priority_spec_init(&pri_spec, dep_stream_id, weight, exclusive);
+
+  return nghttp2_session_open_stream(session, stream_id,
+                                     NGHTTP2_STREAM_FLAG_NONE, &pri_spec,
+                                     NGHTTP2_STREAM_OPENED, NULL);
+}
+
+nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id) {
+  return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 0,
+                              NULL);
+}
+
+nghttp2_stream *open_stream_with_dep(nghttp2_session *session,
+                                     int32_t stream_id,
+                                     nghttp2_stream *dep_stream) {
+  return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 0,
+                              dep_stream);
+}
+
+nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session,
+                                            int32_t stream_id, int32_t weight,
+                                            nghttp2_stream *dep_stream) {
+  return open_stream_with_all(session, stream_id, weight, 0, dep_stream);
+}
+
+nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session,
+                                          int32_t stream_id,
+                                          nghttp2_stream *dep_stream) {
+  return open_stream_with_all(session, stream_id, NGHTTP2_DEFAULT_WEIGHT, 1,
+                              dep_stream);
+}
+
+nghttp2_outbound_item *create_data_ob_item(void) {
+  nghttp2_outbound_item *item;
+
+  item = malloc(sizeof(nghttp2_outbound_item));
+  memset(item, 0, sizeof(nghttp2_outbound_item));
+
+  return item;
+}
diff --git a/tests/nghttp2_test_helper.h b/tests/nghttp2_test_helper.h
new file mode 100644 (file)
index 0000000..36d4476
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * nghttp2 - HTTP/2 C Library
+ *
+ * Copyright (c) 2012 Tatsuhiro Tsujikawa
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef NGHTTP2_TEST_HELPER_H
+#define NGHTTP2_TEST_HELPER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include "nghttp2_frame.h"
+#include "nghttp2_hd.h"
+#include "nghttp2_session.h"
+
+#define MAKE_NV(NAME, VALUE)                                                   \
+  {                                                                            \
+    (uint8_t *) NAME, (uint8_t *)VALUE, strlen(NAME), strlen(VALUE),           \
+        NGHTTP2_NV_FLAG_NONE                                                   \
+  }
+#define ARRLEN(ARR) (sizeof(ARR) / sizeof(ARR[0]))
+
+#define assert_nv_equal(A, B, len)                                             \
+  do {                                                                         \
+    size_t alloclen = sizeof(nghttp2_nv) * len;                                \
+    nghttp2_nv *sa = A, *sb = B;                                               \
+    nghttp2_nv *a = malloc(alloclen);                                          \
+    nghttp2_nv *b = malloc(alloclen);                                          \
+    ssize_t i_;                                                                \
+    memcpy(a, sa, alloclen);                                                   \
+    memcpy(b, sb, alloclen);                                                   \
+    nghttp2_nv_array_sort(a, len);                                             \
+    nghttp2_nv_array_sort(b, len);                                             \
+    for (i_ = 0; i_ < (ssize_t)len; ++i_) {                                    \
+      CU_ASSERT(nghttp2_nv_equal(&a[i_], &b[i_]));                             \
+    }                                                                          \
+    free(b);                                                                   \
+    free(a);                                                                   \
+  } while (0);
+
+int unpack_framebuf(nghttp2_frame *frame, nghttp2_bufs *bufs);
+
+int unpack_frame(nghttp2_frame *frame, const uint8_t *in, size_t len);
+
+int strmemeq(const char *a, const uint8_t *b, size_t bn);
+
+int nvnameeq(const char *a, nghttp2_nv *nv);
+
+int nvvalueeq(const char *a, nghttp2_nv *nv);
+
+typedef struct {
+  nghttp2_nv nva[256];
+  size_t nvlen;
+} nva_out;
+
+void nva_out_init(nva_out *out);
+void nva_out_reset(nva_out *out);
+
+void add_out(nva_out *out, nghttp2_nv *nv);
+
+ssize_t inflate_hd(nghttp2_hd_inflater *inflater, nva_out *out,
+                   nghttp2_bufs *bufs, size_t offset);
+
+int frame_pack_bufs_init(nghttp2_bufs *bufs);
+
+void bufs_large_init(nghttp2_bufs *bufs, size_t chunk_size);
+
+nghttp2_stream *open_stream(nghttp2_session *session, int32_t stream_id);
+
+nghttp2_stream *open_stream_with_dep(nghttp2_session *session,
+                                     int32_t stream_id,
+                                     nghttp2_stream *dep_stream);
+
+nghttp2_stream *open_stream_with_dep_weight(nghttp2_session *session,
+                                            int32_t stream_id, int32_t weight,
+                                            nghttp2_stream *dep_stream);
+
+nghttp2_stream *open_stream_with_dep_excl(nghttp2_session *session,
+                                          int32_t stream_id,
+                                          nghttp2_stream *dep_stream);
+
+nghttp2_outbound_item *create_data_ob_item(void);
+
+#endif /* NGHTTP2_TEST_HELPER_H */
diff --git a/tests/testdata/Makefile.am b/tests/testdata/Makefile.am
new file mode 100644 (file)
index 0000000..ee38113
--- /dev/null
@@ -0,0 +1,23 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2012 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+EXTRA_DIST = cacert.pem  index.html  privkey.pem
diff --git a/tests/testdata/cacert.pem b/tests/testdata/cacert.pem
new file mode 100644 (file)
index 0000000..e462790
--- /dev/null
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICKTCCAdOgAwIBAgIJAIsolheWrwMZMA0GCSqGSIb3DQEBBQUAMHAxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIDAJDQTENMAsGA1UEBwwEQ2l0eTESMBAGA1UECgwJU3Bk
+eSBUZXN0MRIwEAYDVQQDDAlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEWDnNwZHlA
+bG9jYWxob3N0MB4XDTEyMDMwMTE5MTI0NVoXDTIzMDUxOTE5MTI0NVowcDELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgMAkNBMQ0wCwYDVQQHDARDaXR5MRIwEAYDVQQKDAlT
+cGR5IFRlc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYOc3Bk
+eUBsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAw/2MgzAdlJDm29qH
+ZlAibgs9mH+8keOtsRrb4B1PiCcZoHvN9eCVZ4WnzT+0zhHF+nO3YfwVFVC3w7TF
+7fLB3QIDAQABo1AwTjAdBgNVHQ4EFgQUVP2Jw9RX6BB76aV5x2qk5qsrAIQwHwYD
+VR0jBBgwFoAUVP2Jw9RX6BB76aV5x2qk5qsrAIQwDAYDVR0TBAUwAwEB/zANBgkq
+hkiG9w0BAQUFAANBAKd9M5FzQLEZW1KPe9/XNZlgxZ2g3EC5Krxo5I4Ul3MnIYS9
+u4K8t/iprhgOzjFH6+8LVk9v0Za+gU+K43CpUo4=
+-----END CERTIFICATE-----
diff --git a/tests/testdata/index.html b/tests/testdata/index.html
new file mode 100644 (file)
index 0000000..cdd75fc
--- /dev/null
@@ -0,0 +1 @@
+<html><body>small</body></html>
diff --git a/tests/testdata/privkey.pem b/tests/testdata/privkey.pem
new file mode 100644 (file)
index 0000000..0ab825b
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBAMP9jIMwHZSQ5tvah2ZQIm4LPZh/vJHjrbEa2+AdT4gnGaB7zfXg
+lWeFp80/tM4Rxfpzt2H8FRVQt8O0xe3ywd0CAwEAAQJBAIQ8PGP/QNYOdlT8OsLj
+aneJCgQsm1Rro7ONBbFO1WxslvA6+uJsx4Rs8zLiS8cyqmJ/lmGa7zhwYSOvFQPa
+XgECIQDgIcgM/2C67peTm1diKKIoGVVKFCfdRi+Dje6mTl2TQQIhAN/bcFWbG73j
+cUVlIsr9Wk1dJzjPPWKeyirF1qd/WbOdAiEApTsCOeLCssxV3jF02B5QfPNAFx6I
+zO2C9Z7awque/IECIGCHW3VOoTPMs7dc2Rf3D810cclJdArmtf6juOAZRjDxAiBS
+AC+H685IBJ99N5nCbF9NWYIVSkuiKVQ8POYVZX+0Jg==
+-----END RSA PRIVATE KEY-----
diff --git a/third-party/Makefile.am b/third-party/Makefile.am
new file mode 100644 (file)
index 0000000..7549cd6
--- /dev/null
@@ -0,0 +1,29 @@
+# nghttp2 - HTTP/2 C Library
+
+# Copyright (c) 2014 Tatsuhiro Tsujikawa
+
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+AM_CPPFLAGS = -Wall @DEFS@
+
+noinst_LTLIBRARIES = libhttp-parser.la
+libhttp_parser_la_SOURCES = \
+       http-parser/http_parser.c \
+       http-parser/http_parser.h
diff --git a/third-party/http-parser/AUTHORS b/third-party/http-parser/AUTHORS
new file mode 100644 (file)
index 0000000..51b53b1
--- /dev/null
@@ -0,0 +1,50 @@
+# Authors ordered by first contribution.
+Ryan Dahl <ry@tinyclouds.org>
+Jeremy Hinegardner <jeremy@hinegardner.org>
+Sergey Shepelev <temotor@gmail.com>
+Joe Damato <ice799@gmail.com>
+tomika <tomika_nospam@freemail.hu>
+Phoenix Sol <phoenix@burninglabs.com>
+Cliff Frey <cliff@meraki.com>
+Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
+Santiago Gala <sgala@apache.org>
+Tim Becker <tim.becker@syngenio.de>
+Jeff Terrace <jterrace@gmail.com>
+Ben Noordhuis <info@bnoordhuis.nl>
+Nathan Rajlich <nathan@tootallnate.net>
+Mark Nottingham <mnot@mnot.net>
+Aman Gupta <aman@tmm1.net>
+Tim Becker <tim.becker@kuriositaet.de>
+Sean Cunningham <sean.cunningham@mandiant.com>
+Peter Griess <pg@std.in>
+Salman Haq <salman.haq@asti-usa.com>
+Cliff Frey <clifffrey@gmail.com>
+Jon Kolb <jon@b0g.us>
+Fouad Mardini <f.mardini@gmail.com>
+Paul Querna <pquerna@apache.org>
+Felix Geisendörfer <felix@debuggable.com>
+koichik <koichik@improvement.jp>
+Andre Caron <andre.l.caron@gmail.com>
+Ivo Raisr <ivosh@ivosh.net>
+James McLaughlin <jamie@lacewing-project.org>
+David Gwynne <loki@animata.net>
+Thomas LE ROUX <thomas@november-eleven.fr>
+Randy Rizun <rrizun@ortivawireless.com>
+Andre Louis Caron <andre.louis.caron@usherbrooke.ca>
+Simon Zimmermann <simonz05@gmail.com>
+Erik Dubbelboer <erik@dubbelboer.com>
+Martell Malone <martellmalone@gmail.com>
+Bertrand Paquet <bpaquet@octo.com>
+BogDan Vatra <bogdan@kde.org>
+Peter Faiman <peter@thepicard.org>
+Corey Richardson <corey@octayn.net>
+Tóth Tamás <tomika_nospam@freemail.hu>
+Patrik Stutz <patrik.stutz@gmail.com>
+Cam Swords <cam.swords@gmail.com>
+Chris Dickinson <christopher.s.dickinson@gmail.com>
+Uli Köhler <ukoehler@btronik.de>
+Charlie Somerville <charlie@charliesomerville.com>
+Fedor Indutny <fedor.indutny@gmail.com>
+runner <runner.mei@gmail.com>
+Alexis Campailla <alexis@janeasystems.com>
+David Wragg <david@wragg.org>
diff --git a/third-party/http-parser/CONTRIBUTIONS b/third-party/http-parser/CONTRIBUTIONS
new file mode 100644 (file)
index 0000000..11ba31e
--- /dev/null
@@ -0,0 +1,4 @@
+Contributors must agree to the Contributor License Agreement before patches
+can be accepted.
+
+http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
diff --git a/third-party/http-parser/LICENSE-MIT b/third-party/http-parser/LICENSE-MIT
new file mode 100644 (file)
index 0000000..58010b3
--- /dev/null
@@ -0,0 +1,23 @@
+http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
+Igor Sysoev.
+
+Additional changes are licensed under the same terms as NGINX and
+copyright Joyent, Inc. and other Node contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE. 
diff --git a/third-party/http-parser/README.md b/third-party/http-parser/README.md
new file mode 100644 (file)
index 0000000..7c54dd4
--- /dev/null
@@ -0,0 +1,183 @@
+HTTP Parser
+===========
+
+[![Build Status](https://travis-ci.org/joyent/http-parser.png?branch=master)](https://travis-ci.org/joyent/http-parser)
+
+This is a parser for HTTP messages written in C. It parses both requests and
+responses. The parser is designed to be used in performance HTTP
+applications. It does not make any syscalls nor allocations, it does not
+buffer data, it can be interrupted at anytime. Depending on your
+architecture, it only requires about 40 bytes of data per message
+stream (in a web server that is per connection).
+
+Features:
+
+  * No dependencies
+  * Handles persistent streams (keep-alive).
+  * Decodes chunked encoding.
+  * Upgrade support
+  * Defends against buffer overflow attacks.
+
+The parser extracts the following information from HTTP messages:
+
+  * Header fields and values
+  * Content-Length
+  * Request method
+  * Response status code
+  * Transfer-Encoding
+  * HTTP version
+  * Request URL
+  * Message body
+
+
+Usage
+-----
+
+One `http_parser` object is used per TCP connection. Initialize the struct
+using `http_parser_init()` and set the callbacks. That might look something
+like this for a request parser:
+```c
+http_parser_settings settings;
+settings.on_url = my_url_callback;
+settings.on_header_field = my_header_field_callback;
+/* ... */
+
+http_parser *parser = malloc(sizeof(http_parser));
+http_parser_init(parser, HTTP_REQUEST);
+parser->data = my_socket;
+```
+
+When data is received on the socket execute the parser and check for errors.
+
+```c
+size_t len = 80*1024, nparsed;
+char buf[len];
+ssize_t recved;
+
+recved = recv(fd, buf, len, 0);
+
+if (recved < 0) {
+  /* Handle error. */
+}
+
+/* Start up / continue the parser.
+ * Note we pass recved==0 to signal that EOF has been received.
+ */
+nparsed = http_parser_execute(parser, &settings, buf, recved);
+
+if (parser->upgrade) {
+  /* handle new protocol */
+} else if (nparsed != recved) {
+  /* Handle error. Usually just close the connection. */
+}
+```
+
+HTTP needs to know where the end of the stream is. For example, sometimes
+servers send responses without Content-Length and expect the client to
+consume input (for the body) until EOF. To tell http_parser about EOF, give
+`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors
+can still be encountered during an EOF, so one must still be prepared
+to receive them.
+
+Scalar valued message information such as `status_code`, `method`, and the
+HTTP version are stored in the parser structure. This data is only
+temporally stored in `http_parser` and gets reset on each new message. If
+this information is needed later, copy it out of the structure during the
+`headers_complete` callback.
+
+The parser decodes the transfer-encoding for both requests and responses
+transparently. That is, a chunked encoding is decoded before being sent to
+the on_body callback.
+
+
+The Special Problem of Upgrade
+------------------------------
+
+HTTP supports upgrading the connection to a different protocol. An
+increasingly common example of this is the Web Socket protocol which sends
+a request like
+
+        GET /demo HTTP/1.1
+        Upgrade: WebSocket
+        Connection: Upgrade
+        Host: example.com
+        Origin: http://example.com
+        WebSocket-Protocol: sample
+
+followed by non-HTTP data.
+
+(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
+information the Web Socket protocol.)
+
+To support this, the parser will treat this as a normal HTTP message without a
+body, issuing both on_headers_complete and on_message_complete callbacks. However
+http_parser_execute() will stop parsing at the end of the headers and return.
+
+The user is expected to check if `parser->upgrade` has been set to 1 after
+`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
+offset by the return value of `http_parser_execute()`.
+
+
+Callbacks
+---------
+
+During the `http_parser_execute()` call, the callbacks set in
+`http_parser_settings` will be executed. The parser maintains state and
+never looks behind, so buffering the data is not necessary. If you need to
+save certain data for later usage, you can do that from the callbacks.
+
+There are two types of callbacks:
+
+* notification `typedef int (*http_cb) (http_parser*);`
+    Callbacks: on_message_begin, on_headers_complete, on_message_complete.
+* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
+    Callbacks: (requests only) on_url,
+               (common) on_header_field, on_header_value, on_body;
+
+Callbacks must return 0 on success. Returning a non-zero value indicates
+error to the parser, making it exit immediately.
+
+In case you parse HTTP message in chunks (i.e. `read()` request line
+from socket, parse, read half headers, parse, etc) your data callbacks
+may be called more than once. Http-parser guarantees that data pointer is only
+valid for the lifetime of callback. You can also `read()` into a heap allocated
+buffer to avoid copying memory around if this fits your application.
+
+Reading headers may be a tricky task if you read/parse headers partially.
+Basically, you need to remember whether last header callback was field or value
+and apply the following logic:
+
+    (on_header_field and on_header_value shortened to on_h_*)
+     ------------------------ ------------ --------------------------------------------
+    | State (prev. callback) | Callback   | Description/action                         |
+     ------------------------ ------------ --------------------------------------------
+    | nothing (first call)   | on_h_field | Allocate new buffer and copy callback data |
+    |                        |            | into it                                    |
+     ------------------------ ------------ --------------------------------------------
+    | value                  | on_h_field | New header started.                        |
+    |                        |            | Copy current name,value buffers to headers |
+    |                        |            | list and allocate new buffer for new name  |
+     ------------------------ ------------ --------------------------------------------
+    | field                  | on_h_field | Previous name continues. Reallocate name   |
+    |                        |            | buffer and append callback data to it      |
+     ------------------------ ------------ --------------------------------------------
+    | field                  | on_h_value | Value for current header started. Allocate |
+    |                        |            | new buffer and copy callback data to it    |
+     ------------------------ ------------ --------------------------------------------
+    | value                  | on_h_value | Value continues. Reallocate value buffer   |
+    |                        |            | and append callback data to it             |
+     ------------------------ ------------ --------------------------------------------
+
+
+Parsing URLs
+------------
+
+A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`.
+Users of this library may wish to use it to parse URLs constructed from
+consecutive `on_url` callbacks.
+
+See examples of reading in headers:
+
+* [partial example](http://gist.github.com/155877) in C
+* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C
+* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript
diff --git a/third-party/http-parser/bench.c b/third-party/http-parser/bench.c
new file mode 100644 (file)
index 0000000..5b452fa
--- /dev/null
@@ -0,0 +1,111 @@
+/* Copyright Fedor Indutny. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+
+static const char data[] =
+    "POST /joyent/http-parser HTTP/1.1\r\n"
+    "Host: github.com\r\n"
+    "DNT: 1\r\n"
+    "Accept-Encoding: gzip, deflate, sdch\r\n"
+    "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n"
+    "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) "
+        "AppleWebKit/537.36 (KHTML, like Gecko) "
+        "Chrome/39.0.2171.65 Safari/537.36\r\n"
+    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"
+        "image/webp,*/*;q=0.8\r\n"
+    "Referer: https://github.com/joyent/http-parser\r\n"
+    "Connection: keep-alive\r\n"
+    "Transfer-Encoding: chunked\r\n"
+    "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n";
+static const size_t data_len = sizeof(data) - 1;
+
+static int on_info(http_parser* p) {
+  return 0;
+}
+
+
+static int on_data(http_parser* p, const char *at, size_t length) {
+  return 0;
+}
+
+static http_parser_settings settings = {
+  .on_message_begin = on_info,
+  .on_headers_complete = on_info,
+  .on_message_complete = on_info,
+  .on_header_field = on_data,
+  .on_header_value = on_data,
+  .on_url = on_data,
+  .on_status = on_data,
+  .on_body = on_data
+};
+
+int bench(int iter_count, int silent) {
+  struct http_parser parser;
+  int i;
+  int err;
+  struct timeval start;
+  struct timeval end;
+  float rps;
+
+  if (!silent) {
+    err = gettimeofday(&start, NULL);
+    assert(err == 0);
+  }
+
+  for (i = 0; i < iter_count; i++) {
+    size_t parsed;
+    http_parser_init(&parser, HTTP_REQUEST);
+
+    parsed = http_parser_execute(&parser, &settings, data, data_len);
+    assert(parsed == data_len);
+  }
+
+  if (!silent) {
+    err = gettimeofday(&end, NULL);
+    assert(err == 0);
+
+    fprintf(stdout, "Benchmark result:\n");
+
+    rps = (float) (end.tv_sec - start.tv_sec) +
+          (end.tv_usec - start.tv_usec) * 1e-6f;
+    fprintf(stdout, "Took %f seconds to run\n", rps);
+
+    rps = (float) iter_count / rps;
+    fprintf(stdout, "%f req/sec\n", rps);
+    fflush(stdout);
+  }
+
+  return 0;
+}
+
+int main(int argc, char** argv) {
+  if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
+    for (;;)
+      bench(5000000, 1);
+    return 0;
+  } else {
+    return bench(5000000, 0);
+  }
+}
diff --git a/third-party/http-parser/contrib/parsertrace.c b/third-party/http-parser/contrib/parsertrace.c
new file mode 100644 (file)
index 0000000..e715368
--- /dev/null
@@ -0,0 +1,160 @@
+/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
+ *
+ * Additional changes are licensed under the same terms as NGINX and
+ * copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* Dump what the parser finds to stdout as it happen */
+
+#include "http_parser.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int on_message_begin(http_parser* _) {
+  (void)_;
+  printf("\n***MESSAGE BEGIN***\n\n");
+  return 0;
+}
+
+int on_headers_complete(http_parser* _) {
+  (void)_;
+  printf("\n***HEADERS COMPLETE***\n\n");
+  return 0;
+}
+
+int on_message_complete(http_parser* _) {
+  (void)_;
+  printf("\n***MESSAGE COMPLETE***\n\n");
+  return 0;
+}
+
+int on_url(http_parser* _, const char* at, size_t length) {
+  (void)_;
+  printf("Url: %.*s\n", (int)length, at);
+  return 0;
+}
+
+int on_header_field(http_parser* _, const char* at, size_t length) {
+  (void)_;
+  printf("Header field: %.*s\n", (int)length, at);
+  return 0;
+}
+
+int on_header_value(http_parser* _, const char* at, size_t length) {
+  (void)_;
+  printf("Header value: %.*s\n", (int)length, at);
+  return 0;
+}
+
+int on_body(http_parser* _, const char* at, size_t length) {
+  (void)_;
+  printf("Body: %.*s\n", (int)length, at);
+  return 0;
+}
+
+void usage(const char* name) {
+  fprintf(stderr,
+          "Usage: %s $type $filename\n"
+          "  type: -x, where x is one of {r,b,q}\n"
+          "  parses file as a Response, reQuest, or Both\n",
+          name);
+  exit(EXIT_FAILURE);
+}
+
+int main(int argc, char* argv[]) {
+  enum http_parser_type file_type;
+
+  if (argc != 3) {
+    usage(argv[0]);
+  }
+
+  char* type = argv[1];
+  if (type[0] != '-') {
+    usage(argv[0]);
+  }
+
+  switch (type[1]) {
+    /* in the case of "-", type[1] will be NUL */
+    case 'r':
+      file_type = HTTP_RESPONSE;
+      break;
+    case 'q':
+      file_type = HTTP_REQUEST;
+      break;
+    case 'b':
+      file_type = HTTP_BOTH;
+      break;
+    default:
+      usage(argv[0]);
+  }
+
+  char* filename = argv[2];
+  FILE* file = fopen(filename, "r");
+  if (file == NULL) {
+    perror("fopen");
+    goto fail;
+  }
+
+  fseek(file, 0, SEEK_END);
+  long file_length = ftell(file);
+  if (file_length == -1) {
+    perror("ftell");
+    goto fail;
+  }
+  fseek(file, 0, SEEK_SET);
+
+  char* data = malloc(file_length);
+  if (fread(data, 1, file_length, file) != (size_t)file_length) {
+    fprintf(stderr, "couldn't read entire file\n");
+    free(data);
+    goto fail;
+  }
+
+  http_parser_settings settings;
+  memset(&settings, 0, sizeof(settings));
+  settings.on_message_begin = on_message_begin;
+  settings.on_url = on_url;
+  settings.on_header_field = on_header_field;
+  settings.on_header_value = on_header_value;
+  settings.on_headers_complete = on_headers_complete;
+  settings.on_body = on_body;
+  settings.on_message_complete = on_message_complete;
+
+  http_parser parser;
+  http_parser_init(&parser, file_type);
+  size_t nparsed = http_parser_execute(&parser, &settings, data, file_length);
+  free(data);
+
+  if (nparsed != (size_t)file_length) {
+    fprintf(stderr,
+            "Error: %s (%s)\n",
+            http_errno_description(HTTP_PARSER_ERRNO(&parser)),
+            http_errno_name(HTTP_PARSER_ERRNO(&parser)));
+    goto fail;
+  }
+
+  return EXIT_SUCCESS;
+
+fail:
+  fclose(file);
+  return EXIT_FAILURE;
+}
diff --git a/third-party/http-parser/contrib/url_parser.c b/third-party/http-parser/contrib/url_parser.c
new file mode 100644 (file)
index 0000000..b1f9c97
--- /dev/null
@@ -0,0 +1,44 @@
+#include "http_parser.h"
+#include <stdio.h>
+#include <string.h>
+
+void
+dump_url (const char *url, const struct http_parser_url *u)
+{
+  unsigned int i;
+
+  printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
+  for (i = 0; i < UF_MAX; i++) {
+    if ((u->field_set & (1 << i)) == 0) {
+      printf("\tfield_data[%u]: unset\n", i);
+      continue;
+    }
+
+    printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n",
+           i,
+           u->field_data[i].off,
+           u->field_data[i].len,
+           u->field_data[i].len,
+           url + u->field_data[i].off);
+  }
+}
+
+int main(int argc, char ** argv) {
+  if (argc != 3) {
+    printf("Syntax : %s connect|get url\n", argv[0]);
+    return 1;
+  }
+  struct http_parser_url u;
+  int len = strlen(argv[2]);
+  int connect = strcmp("connect", argv[1]) == 0 ? 1 : 0;
+  printf("Parsing %s, connect %d\n", argv[2], connect);
+
+  int result = http_parser_parse_url(argv[2], len, connect, &u);
+  if (result != 0) {
+    printf("Parse error : %d\n", result);
+    return result;
+  }
+  printf("Parse ok, result : \n");
+  dump_url(argv[2], &u);
+  return 0;
+}
\ No newline at end of file
diff --git a/third-party/http-parser/http_parser.c b/third-party/http-parser/http_parser.c
new file mode 100644 (file)
index 0000000..23077d1
--- /dev/null
@@ -0,0 +1,2410 @@
+/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
+ *
+ * Additional changes are licensed under the same terms as NGINX and
+ * copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <assert.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#ifndef ULLONG_MAX
+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef BIT_AT
+# define BIT_AT(a, i)                                                \
+  (!!((unsigned int) (a)[(unsigned int) (i) >> 3] &                  \
+   (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#ifndef ELEM_AT
+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
+#endif
+
+#define SET_ERRNO(e)                                                 \
+do {                                                                 \
+  parser->http_errno = (e);                                          \
+} while(0)
+
+#define CURRENT_STATE() p_state
+#define UPDATE_STATE(V) p_state = (V);
+#define RETURN(V)                                                    \
+do {                                                                 \
+  parser->state = CURRENT_STATE();                                   \
+  return (V);                                                        \
+} while (0);
+#define REEXECUTE()                                                  \
+  --p;                                                               \
+  break;
+
+
+#ifdef __GNUC__
+# define LIKELY(X) __builtin_expect(!!(X), 1)
+# define UNLIKELY(X) __builtin_expect(!!(X), 0)
+#else
+# define LIKELY(X) (X)
+# define UNLIKELY(X) (X)
+#endif
+
+
+/* Run the notify callback FOR, returning ER if it fails */
+#define CALLBACK_NOTIFY_(FOR, ER)                                    \
+do {                                                                 \
+  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
+                                                                     \
+  if (LIKELY(settings->on_##FOR)) {                                  \
+    parser->state = CURRENT_STATE();                                 \
+    if (UNLIKELY(0 != settings->on_##FOR(parser))) {                 \
+      SET_ERRNO(HPE_CB_##FOR);                                       \
+    }                                                                \
+    UPDATE_STATE(parser->state);                                     \
+                                                                     \
+    /* We either errored above or got paused; get out */             \
+    if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {             \
+      return (ER);                                                   \
+    }                                                                \
+  }                                                                  \
+} while (0)
+
+/* Run the notify callback FOR and consume the current byte */
+#define CALLBACK_NOTIFY(FOR)            CALLBACK_NOTIFY_(FOR, p - data + 1)
+
+/* Run the notify callback FOR and don't consume the current byte */
+#define CALLBACK_NOTIFY_NOADVANCE(FOR)  CALLBACK_NOTIFY_(FOR, p - data)
+
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CALLBACK_DATA_(FOR, LEN, ER)                                 \
+do {                                                                 \
+  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
+                                                                     \
+  if (FOR##_mark) {                                                  \
+    if (LIKELY(settings->on_##FOR)) {                                \
+      parser->state = CURRENT_STATE();                               \
+      if (UNLIKELY(0 !=                                              \
+                   settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \
+        SET_ERRNO(HPE_CB_##FOR);                                     \
+      }                                                              \
+      UPDATE_STATE(parser->state);                                   \
+                                                                     \
+      /* We either errored above or got paused; get out */           \
+      if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {           \
+        return (ER);                                                 \
+      }                                                              \
+    }                                                                \
+    FOR##_mark = NULL;                                               \
+  }                                                                  \
+} while (0)
+  
+/* Run the data callback FOR and consume the current byte */
+#define CALLBACK_DATA(FOR)                                           \
+    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CALLBACK_DATA_NOADVANCE(FOR)                                 \
+    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define MARK(FOR)                                                    \
+do {                                                                 \
+  if (!FOR##_mark) {                                                 \
+    FOR##_mark = p;                                                  \
+  }                                                                  \
+} while (0)
+
+/* Don't allow the total size of the HTTP headers (including the status
+ * line) to exceed HTTP_MAX_HEADER_SIZE.  This check is here to protect
+ * embedders against denial-of-service attacks where the attacker feeds
+ * us a never-ending header that the embedder keeps buffering.
+ *
+ * This check is arguably the responsibility of embedders but we're doing
+ * it on the embedder's behalf because most won't bother and this way we
+ * make the web a little safer.  HTTP_MAX_HEADER_SIZE is still far bigger
+ * than any reasonable request or response so this should never affect
+ * day-to-day operation.
+ */
+#define COUNT_HEADER_SIZE(V)                                         \
+do {                                                                 \
+  parser->nread += (V);                                              \
+  if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) {            \
+    SET_ERRNO(HPE_HEADER_OVERFLOW);                                  \
+    goto error;                                                      \
+  }                                                                  \
+} while (0)
+
+
+#define PROXY_CONNECTION "proxy-connection"
+#define CONNECTION "connection"
+#define CONTENT_LENGTH "content-length"
+#define TRANSFER_ENCODING "transfer-encoding"
+#define UPGRADE "upgrade"
+#define CHUNKED "chunked"
+#define KEEP_ALIVE "keep-alive"
+#define CLOSE "close"
+
+
+static const char *method_strings[] =
+  {
+#define XX(num, name, string) #string,
+  HTTP_METHOD_MAP(XX)
+#undef XX
+  };
+
+
+/* Tokens as defined by rfc 2616. Also lowercases them.
+ *        token       = 1*<any CHAR except CTLs or separators>
+ *     separators     = "(" | ")" | "<" | ">" | "@"
+ *                    | "," | ";" | ":" | "\" | <">
+ *                    | "/" | "[" | "]" | "?" | "="
+ *                    | "{" | "}" | SP | HT
+ */
+static const char tokens[256] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0,       0,       0,       0,       0,       0,       0,       0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+        0,      '!',      0,      '#',     '$',     '%',     '&',    '\'',
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        0,       0,      '*',     '+',      0,      '-',     '.',      0,
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+       '8',     '9',      0,       0,       0,       0,       0,       0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+       'x',     'y',     'z',      0,       0,       0,      '^',     '_',
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+       'x',     'y',     'z',      0,      '|',      0,      '~',       0 };
+
+
+static const int8_t unhex[256] =
+  {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
+  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
+  };
+
+
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
+/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
+        0    | T(2)   |   0    |   0    | T(16)  |   0    |   0    |   0,
+/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
+        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
+/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
+        0    |   2    |   4    |   0    |   16   |   32   |   64   |  128,
+/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0,
+/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
+/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
+        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0, };
+
+#undef T
+
+enum state
+  { s_dead = 1 /* important that this is > 0 */
+
+  , s_start_req_or_res
+  , s_res_or_resp_H
+  , s_start_res
+  , s_res_H
+  , s_res_HT
+  , s_res_HTT
+  , s_res_HTTP
+  , s_res_first_http_major
+  , s_res_http_major
+  , s_res_first_http_minor
+  , s_res_http_minor
+  , s_res_first_status_code
+  , s_res_status_code
+  , s_res_status_start
+  , s_res_status
+  , s_res_line_almost_done
+
+  , s_start_req
+
+  , s_req_method
+  , s_req_spaces_before_url
+  , s_req_schema
+  , s_req_schema_slash
+  , s_req_schema_slash_slash
+  , s_req_server_start
+  , s_req_server
+  , s_req_server_with_at
+  , s_req_path
+  , s_req_query_string_start
+  , s_req_query_string
+  , s_req_fragment_start
+  , s_req_fragment
+  , s_req_http_start
+  , s_req_http_H
+  , s_req_http_HT
+  , s_req_http_HTT
+  , s_req_http_HTTP
+  , s_req_first_http_major
+  , s_req_http_major
+  , s_req_first_http_minor
+  , s_req_http_minor
+  , s_req_line_almost_done
+
+  , s_header_field_start
+  , s_header_field
+  , s_header_value_discard_ws
+  , s_header_value_discard_ws_almost_done
+  , s_header_value_discard_lws
+  , s_header_value_start
+  , s_header_value
+  , s_header_value_lws
+
+  , s_header_almost_done
+
+  , s_chunk_size_start
+  , s_chunk_size
+  , s_chunk_parameters
+  , s_chunk_size_almost_done
+
+  , s_headers_almost_done
+  , s_headers_done
+
+  /* Important: 's_headers_done' must be the last 'header' state. All
+   * states beyond this must be 'body' states. It is used for overflow
+   * checking. See the PARSING_HEADER() macro.
+   */
+
+  , s_chunk_data
+  , s_chunk_data_almost_done
+  , s_chunk_data_done
+
+  , s_body_identity
+  , s_body_identity_eof
+
+  , s_message_done
+  };
+
+
+#define PARSING_HEADER(state) (state <= s_headers_done)
+
+
+enum header_states
+  { h_general = 0
+  , h_C
+  , h_CO
+  , h_CON
+
+  , h_matching_connection
+  , h_matching_proxy_connection
+  , h_matching_content_length
+  , h_matching_transfer_encoding
+  , h_matching_upgrade
+
+  , h_connection
+  , h_content_length
+  , h_transfer_encoding
+  , h_upgrade
+
+  , h_matching_transfer_encoding_chunked
+  , h_matching_connection_token_start
+  , h_matching_connection_keep_alive
+  , h_matching_connection_close
+  , h_matching_connection_upgrade
+  , h_matching_connection_token
+
+  , h_transfer_encoding_chunked
+  , h_connection_keep_alive
+  , h_connection_close
+  , h_connection_upgrade
+  };
+
+enum http_host_state
+  {
+    s_http_host_dead = 1
+  , s_http_userinfo_start
+  , s_http_userinfo
+  , s_http_host_start
+  , s_http_host_v6_start
+  , s_http_host
+  , s_http_host_v6
+  , s_http_host_v6_end
+  , s_http_host_port_start
+  , s_http_host_port
+};
+
+/* Macros for character classes; depends on strict-mode  */
+#define CR                  '\r'
+#define LF                  '\n'
+#define LOWER(c)            (unsigned char)(c | 0x20)
+#define IS_ALPHA(c)         (LOWER(c) >= 'a' && LOWER(c) <= 'z')
+#define IS_NUM(c)           ((c) >= '0' && (c) <= '9')
+#define IS_ALPHANUM(c)      (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c)           (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c)          ((c) == '-' || (c) == '_' || (c) == '.' || \
+  (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+  (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+  (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+  (c) == '$' || (c) == ',')
+
+#define STRICT_TOKEN(c)     (tokens[(unsigned char)c])
+
+#if HTTP_PARSER_STRICT
+#define TOKEN(c)            (tokens[(unsigned char)c])
+#define IS_URL_CHAR(c)      (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
+#else
+#define TOKEN(c)            ((c == ' ') ? ' ' : tokens[(unsigned char)c])
+#define IS_URL_CHAR(c)                                                         \
+  (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
+#define IS_HOST_CHAR(c)                                                        \
+  (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+
+#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
+
+
+#if HTTP_PARSER_STRICT
+# define STRICT_CHECK(cond)                                          \
+do {                                                                 \
+  if (cond) {                                                        \
+    SET_ERRNO(HPE_STRICT);                                           \
+    goto error;                                                      \
+  }                                                                  \
+} while (0)
+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
+#else
+# define STRICT_CHECK(cond)
+# define NEW_MESSAGE() start_state
+#endif
+
+
+/* Map errno values to strings for human-readable output */
+#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
+static struct {
+  const char *name;
+  const char *description;
+} http_strerror_tab[] = {
+  HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
+};
+#undef HTTP_STRERROR_GEN
+
+int http_message_needs_eof(const http_parser *parser);
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+  if (ch == ' ' || ch == '\r' || ch == '\n') {
+    return s_dead;
+  }
+
+#if HTTP_PARSER_STRICT
+  if (ch == '\t' || ch == '\f') {
+    return s_dead;
+  }
+#endif
+
+  switch (s) {
+    case s_req_spaces_before_url:
+      /* Proxied requests are followed by scheme of an absolute URI (alpha).
+       * All methods except CONNECT are followed by '/' or '*'.
+       */
+
+      if (ch == '/' || ch == '*') {
+        return s_req_path;
+      }
+
+      if (IS_ALPHA(ch)) {
+        return s_req_schema;
+      }
+
+      break;
+
+    case s_req_schema:
+      if (IS_ALPHA(ch)) {
+        return s;
+      }
+
+      if (ch == ':') {
+        return s_req_schema_slash;
+      }
+
+      break;
+
+    case s_req_schema_slash:
+      if (ch == '/') {
+        return s_req_schema_slash_slash;
+      }
+
+      break;
+
+    case s_req_schema_slash_slash:
+      if (ch == '/') {
+        return s_req_server_start;
+      }
+
+      break;
+
+    case s_req_server_with_at:
+      if (ch == '@') {
+        return s_dead;
+      }
+
+    /* FALLTHROUGH */
+    case s_req_server_start:
+    case s_req_server:
+      if (ch == '/') {
+        return s_req_path;
+      }
+
+      if (ch == '?') {
+        return s_req_query_string_start;
+      }
+
+      if (ch == '@') {
+        return s_req_server_with_at;
+      }
+
+      if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+        return s_req_server;
+      }
+
+      break;
+
+    case s_req_path:
+      if (IS_URL_CHAR(ch)) {
+        return s;
+      }
+
+      switch (ch) {
+        case '?':
+          return s_req_query_string_start;
+
+        case '#':
+          return s_req_fragment_start;
+      }
+
+      break;
+
+    case s_req_query_string_start:
+    case s_req_query_string:
+      if (IS_URL_CHAR(ch)) {
+        return s_req_query_string;
+      }
+
+      switch (ch) {
+        case '?':
+          /* allow extra '?' in query string */
+          return s_req_query_string;
+
+        case '#':
+          return s_req_fragment_start;
+      }
+
+      break;
+
+    case s_req_fragment_start:
+      if (IS_URL_CHAR(ch)) {
+        return s_req_fragment;
+      }
+
+      switch (ch) {
+        case '?':
+          return s_req_fragment;
+
+        case '#':
+          return s;
+      }
+
+      break;
+
+    case s_req_fragment:
+      if (IS_URL_CHAR(ch)) {
+        return s;
+      }
+
+      switch (ch) {
+        case '?':
+        case '#':
+          return s;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+
+  /* We should never fall out of the switch above unless there's an error */
+  return s_dead;
+}
+
+size_t http_parser_execute (http_parser *parser,
+                            const http_parser_settings *settings,
+                            const char *data,
+                            size_t len)
+{
+  char c, ch;
+  int8_t unhex_val;
+  const char *p = data;
+  const char *header_field_mark = 0;
+  const char *header_value_mark = 0;
+  const char *url_mark = 0;
+  const char *body_mark = 0;
+  const char *status_mark = 0;
+  enum state p_state = parser->state;
+
+  /* We're in an error state. Don't bother doing anything. */
+  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+    return 0;
+  }
+
+  if (len == 0) {
+    switch (CURRENT_STATE()) {
+      case s_body_identity_eof:
+        /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
+         * we got paused.
+         */
+        CALLBACK_NOTIFY_NOADVANCE(message_complete);
+        return 0;
+
+      case s_dead:
+      case s_start_req_or_res:
+      case s_start_res:
+      case s_start_req:
+        return 0;
+
+      default:
+        SET_ERRNO(HPE_INVALID_EOF_STATE);
+        return 1;
+    }
+  }
+
+
+  if (CURRENT_STATE() == s_header_field)
+    header_field_mark = data;
+  if (CURRENT_STATE() == s_header_value)
+    header_value_mark = data;
+  switch (CURRENT_STATE()) {
+  case s_req_path:
+  case s_req_schema:
+  case s_req_schema_slash:
+  case s_req_schema_slash_slash:
+  case s_req_server_start:
+  case s_req_server:
+  case s_req_server_with_at:
+  case s_req_query_string_start:
+  case s_req_query_string:
+  case s_req_fragment_start:
+  case s_req_fragment:
+    url_mark = data;
+    break;
+  case s_res_status:
+    status_mark = data;
+    break;
+  default:
+    break;
+  }
+
+  for (p=data; p != data + len; p++) {
+    ch = *p;
+
+    if (PARSING_HEADER(CURRENT_STATE()))
+      COUNT_HEADER_SIZE(1);
+
+    switch (CURRENT_STATE()) {
+
+      case s_dead:
+        /* this state is used after a 'Connection: close' message
+         * the parser will error out if it reads another message
+         */
+        if (LIKELY(ch == CR || ch == LF))
+          break;
+
+        SET_ERRNO(HPE_CLOSED_CONNECTION);
+        goto error;
+
+      case s_start_req_or_res:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = ULLONG_MAX;
+
+        if (ch == 'H') {
+          UPDATE_STATE(s_res_or_resp_H);
+
+          CALLBACK_NOTIFY(message_begin);
+        } else {
+          parser->type = HTTP_REQUEST;
+          UPDATE_STATE(s_start_req);
+          REEXECUTE();
+        }
+
+        break;
+      }
+
+      case s_res_or_resp_H:
+        if (ch == 'T') {
+          parser->type = HTTP_RESPONSE;
+          UPDATE_STATE(s_res_HT);
+        } else {
+          if (UNLIKELY(ch != 'E')) {
+            SET_ERRNO(HPE_INVALID_CONSTANT);
+            goto error;
+          }
+
+          parser->type = HTTP_REQUEST;
+          parser->method = HTTP_HEAD;
+          parser->index = 2;
+          UPDATE_STATE(s_req_method);
+        }
+        break;
+
+      case s_start_res:
+      {
+        parser->flags = 0;
+        parser->content_length = ULLONG_MAX;
+
+        switch (ch) {
+          case 'H':
+            UPDATE_STATE(s_res_H);
+            break;
+
+          case CR:
+          case LF:
+            break;
+
+          default:
+            SET_ERRNO(HPE_INVALID_CONSTANT);
+            goto error;
+        }
+
+        CALLBACK_NOTIFY(message_begin);
+        break;
+      }
+
+      case s_res_H:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_res_HT);
+        break;
+
+      case s_res_HT:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_res_HTT);
+        break;
+
+      case s_res_HTT:
+        STRICT_CHECK(ch != 'P');
+        UPDATE_STATE(s_res_HTTP);
+        break;
+
+      case s_res_HTTP:
+        STRICT_CHECK(ch != '/');
+        UPDATE_STATE(s_res_first_http_major);
+        break;
+
+      case s_res_first_http_major:
+        if (UNLIKELY(ch < '0' || ch > '9')) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_major = ch - '0';
+        UPDATE_STATE(s_res_http_major);
+        break;
+
+      /* major HTTP version or dot */
+      case s_res_http_major:
+      {
+        if (ch == '.') {
+          UPDATE_STATE(s_res_first_http_minor);
+          break;
+        }
+
+        if (!IS_NUM(ch)) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_major *= 10;
+        parser->http_major += ch - '0';
+
+        if (UNLIKELY(parser->http_major > 999)) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        break;
+      }
+
+      /* first digit of minor HTTP version */
+      case s_res_first_http_minor:
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_minor = ch - '0';
+        UPDATE_STATE(s_res_http_minor);
+        break;
+
+      /* minor HTTP version or end of request line */
+      case s_res_http_minor:
+      {
+        if (ch == ' ') {
+          UPDATE_STATE(s_res_first_status_code);
+          break;
+        }
+
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_minor *= 10;
+        parser->http_minor += ch - '0';
+
+        if (UNLIKELY(parser->http_minor > 999)) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        break;
+      }
+
+      case s_res_first_status_code:
+      {
+        if (!IS_NUM(ch)) {
+          if (ch == ' ') {
+            break;
+          }
+
+          SET_ERRNO(HPE_INVALID_STATUS);
+          goto error;
+        }
+        parser->status_code = ch - '0';
+        UPDATE_STATE(s_res_status_code);
+        break;
+      }
+
+      case s_res_status_code:
+      {
+        if (!IS_NUM(ch)) {
+          switch (ch) {
+            case ' ':
+              UPDATE_STATE(s_res_status_start);
+              break;
+            case CR:
+              UPDATE_STATE(s_res_line_almost_done);
+              break;
+            case LF:
+              UPDATE_STATE(s_header_field_start);
+              break;
+            default:
+              SET_ERRNO(HPE_INVALID_STATUS);
+              goto error;
+          }
+          break;
+        }
+
+        parser->status_code *= 10;
+        parser->status_code += ch - '0';
+
+        if (UNLIKELY(parser->status_code > 999)) {
+          SET_ERRNO(HPE_INVALID_STATUS);
+          goto error;
+        }
+
+        break;
+      }
+
+      case s_res_status_start:
+      {
+        if (ch == CR) {
+          UPDATE_STATE(s_res_line_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_field_start);
+          break;
+        }
+
+        MARK(status);
+        UPDATE_STATE(s_res_status);
+        parser->index = 0;
+        break;
+      }
+
+      case s_res_status:
+        if (ch == CR) {
+          UPDATE_STATE(s_res_line_almost_done);
+          CALLBACK_DATA(status);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_field_start);
+          CALLBACK_DATA(status);
+          break;
+        }
+
+        break;
+
+      case s_res_line_almost_done:
+        STRICT_CHECK(ch != LF);
+        UPDATE_STATE(s_header_field_start);
+        break;
+
+      case s_start_req:
+      {
+        if (ch == CR || ch == LF)
+          break;
+        parser->flags = 0;
+        parser->content_length = ULLONG_MAX;
+
+        if (UNLIKELY(!IS_ALPHA(ch))) {
+          SET_ERRNO(HPE_INVALID_METHOD);
+          goto error;
+        }
+
+        parser->method = (enum http_method) 0;
+        parser->index = 1;
+        switch (ch) {
+          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
+          case 'D': parser->method = HTTP_DELETE; break;
+          case 'G': parser->method = HTTP_GET; break;
+          case 'H': parser->method = HTTP_HEAD; break;
+          case 'L': parser->method = HTTP_LOCK; break;
+          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
+          case 'N': parser->method = HTTP_NOTIFY; break;
+          case 'O': parser->method = HTTP_OPTIONS; break;
+          case 'P': parser->method = HTTP_POST;
+            /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
+            break;
+          case 'R': parser->method = HTTP_REPORT; break;
+          case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
+          case 'T': parser->method = HTTP_TRACE; break;
+          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
+          default:
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+        }
+        UPDATE_STATE(s_req_method);
+
+        CALLBACK_NOTIFY(message_begin);
+
+        break;
+      }
+
+      case s_req_method:
+      {
+        const char *matcher;
+        if (UNLIKELY(ch == '\0')) {
+          SET_ERRNO(HPE_INVALID_METHOD);
+          goto error;
+        }
+
+        matcher = method_strings[parser->method];
+        if (ch == ' ' && matcher[parser->index] == '\0') {
+          UPDATE_STATE(s_req_spaces_before_url);
+        } else if (ch == matcher[parser->index]) {
+          ; /* nada */
+        } else if (parser->method == HTTP_CONNECT) {
+          if (parser->index == 1 && ch == 'H') {
+            parser->method = HTTP_CHECKOUT;
+          } else if (parser->index == 2  && ch == 'P') {
+            parser->method = HTTP_COPY;
+          } else {
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+          }
+        } else if (parser->method == HTTP_MKCOL) {
+          if (parser->index == 1 && ch == 'O') {
+            parser->method = HTTP_MOVE;
+          } else if (parser->index == 1 && ch == 'E') {
+            parser->method = HTTP_MERGE;
+          } else if (parser->index == 1 && ch == '-') {
+            parser->method = HTTP_MSEARCH;
+          } else if (parser->index == 2 && ch == 'A') {
+            parser->method = HTTP_MKACTIVITY;
+          } else if (parser->index == 3 && ch == 'A') {
+            parser->method = HTTP_MKCALENDAR;
+          } else {
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+          }
+        } else if (parser->method == HTTP_SUBSCRIBE) {
+          if (parser->index == 1 && ch == 'E') {
+            parser->method = HTTP_SEARCH;
+          } else {
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+          }
+        } else if (parser->index == 1 && parser->method == HTTP_POST) {
+          if (ch == 'R') {
+            parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
+          } else if (ch == 'U') {
+            parser->method = HTTP_PUT; /* or HTTP_PURGE */
+          } else if (ch == 'A') {
+            parser->method = HTTP_PATCH;
+          } else {
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+          }
+        } else if (parser->index == 2) {
+          if (parser->method == HTTP_PUT) {
+            if (ch == 'R') {
+              parser->method = HTTP_PURGE;
+            } else {
+              SET_ERRNO(HPE_INVALID_METHOD);
+              goto error;
+            }
+          } else if (parser->method == HTTP_UNLOCK) {
+            if (ch == 'S') {
+              parser->method = HTTP_UNSUBSCRIBE;
+            } else {
+              SET_ERRNO(HPE_INVALID_METHOD);
+              goto error;
+            }
+          } else {
+            SET_ERRNO(HPE_INVALID_METHOD);
+            goto error;
+          }
+        } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
+          parser->method = HTTP_PROPPATCH;
+        } else {
+          SET_ERRNO(HPE_INVALID_METHOD);
+          goto error;
+        }
+
+        ++parser->index;
+        break;
+      }
+
+      case s_req_spaces_before_url:
+      {
+        if (ch == ' ') break;
+
+        MARK(url);
+        if (parser->method == HTTP_CONNECT) {
+          UPDATE_STATE(s_req_server_start);
+        }
+
+        UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+        if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+          SET_ERRNO(HPE_INVALID_URL);
+          goto error;
+        }
+
+        break;
+      }
+
+      case s_req_schema:
+      case s_req_schema_slash:
+      case s_req_schema_slash_slash:
+      case s_req_server_start:
+      {
+        switch (ch) {
+          /* No whitespace allowed here */
+          case ' ':
+          case CR:
+          case LF:
+            SET_ERRNO(HPE_INVALID_URL);
+            goto error;
+          default:
+            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+              SET_ERRNO(HPE_INVALID_URL);
+              goto error;
+            }
+        }
+
+        break;
+      }
+
+      case s_req_server:
+      case s_req_server_with_at:
+      case s_req_path:
+      case s_req_query_string_start:
+      case s_req_query_string:
+      case s_req_fragment_start:
+      case s_req_fragment:
+      {
+        switch (ch) {
+          case ' ':
+            UPDATE_STATE(s_req_http_start);
+            CALLBACK_DATA(url);
+            break;
+          case CR:
+          case LF:
+            parser->http_major = 0;
+            parser->http_minor = 9;
+            UPDATE_STATE((ch == CR) ?
+              s_req_line_almost_done :
+              s_header_field_start);
+            CALLBACK_DATA(url);
+            break;
+          default:
+            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
+            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
+              SET_ERRNO(HPE_INVALID_URL);
+              goto error;
+            }
+        }
+        break;
+      }
+
+      case s_req_http_start:
+        switch (ch) {
+          case 'H':
+            UPDATE_STATE(s_req_http_H);
+            break;
+          case ' ':
+            break;
+          default:
+            SET_ERRNO(HPE_INVALID_CONSTANT);
+            goto error;
+        }
+        break;
+
+      case s_req_http_H:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_req_http_HT);
+        break;
+
+      case s_req_http_HT:
+        STRICT_CHECK(ch != 'T');
+        UPDATE_STATE(s_req_http_HTT);
+        break;
+
+      case s_req_http_HTT:
+        STRICT_CHECK(ch != 'P');
+        UPDATE_STATE(s_req_http_HTTP);
+        break;
+
+      case s_req_http_HTTP:
+        STRICT_CHECK(ch != '/');
+        UPDATE_STATE(s_req_first_http_major);
+        break;
+
+      /* first digit of major HTTP version */
+      case s_req_first_http_major:
+        if (UNLIKELY(ch < '1' || ch > '9')) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_major = ch - '0';
+        UPDATE_STATE(s_req_http_major);
+        break;
+
+      /* major HTTP version or dot */
+      case s_req_http_major:
+      {
+        if (ch == '.') {
+          UPDATE_STATE(s_req_first_http_minor);
+          break;
+        }
+
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_major *= 10;
+        parser->http_major += ch - '0';
+
+        if (UNLIKELY(parser->http_major > 999)) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        break;
+      }
+
+      /* first digit of minor HTTP version */
+      case s_req_first_http_minor:
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_minor = ch - '0';
+        UPDATE_STATE(s_req_http_minor);
+        break;
+
+      /* minor HTTP version or end of request line */
+      case s_req_http_minor:
+      {
+        if (ch == CR) {
+          UPDATE_STATE(s_req_line_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_field_start);
+          break;
+        }
+
+        /* XXX allow spaces after digit? */
+
+        if (UNLIKELY(!IS_NUM(ch))) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        parser->http_minor *= 10;
+        parser->http_minor += ch - '0';
+
+        if (UNLIKELY(parser->http_minor > 999)) {
+          SET_ERRNO(HPE_INVALID_VERSION);
+          goto error;
+        }
+
+        break;
+      }
+
+      /* end of request line */
+      case s_req_line_almost_done:
+      {
+        if (UNLIKELY(ch != LF)) {
+          SET_ERRNO(HPE_LF_EXPECTED);
+          goto error;
+        }
+
+        UPDATE_STATE(s_header_field_start);
+        break;
+      }
+
+      case s_header_field_start:
+      {
+        if (ch == CR) {
+          UPDATE_STATE(s_headers_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          /* they might be just sending \n instead of \r\n so this would be
+           * the second \n to denote the end of headers*/
+          UPDATE_STATE(s_headers_almost_done);
+          REEXECUTE();
+        }
+
+        c = TOKEN(ch);
+
+        if (UNLIKELY(!c)) {
+          SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+          goto error;
+        }
+
+        MARK(header_field);
+
+        parser->index = 0;
+        UPDATE_STATE(s_header_field);
+
+        switch (c) {
+          case 'c':
+            parser->header_state = h_C;
+            break;
+
+          case 'p':
+            parser->header_state = h_matching_proxy_connection;
+            break;
+
+          case 't':
+            parser->header_state = h_matching_transfer_encoding;
+            break;
+
+          case 'u':
+            parser->header_state = h_matching_upgrade;
+            break;
+
+          default:
+            parser->header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_field:
+      {
+        const char* start = p;
+        for (; p != data + len; p++) {
+          ch = *p;
+          c = TOKEN(ch);
+
+          if (!c)
+            break;
+
+          switch (parser->header_state) {
+            case h_general:
+              break;
+
+            case h_C:
+              parser->index++;
+              parser->header_state = (c == 'o' ? h_CO : h_general);
+              break;
+
+            case h_CO:
+              parser->index++;
+              parser->header_state = (c == 'n' ? h_CON : h_general);
+              break;
+
+            case h_CON:
+              parser->index++;
+              switch (c) {
+                case 'n':
+                  parser->header_state = h_matching_connection;
+                  break;
+                case 't':
+                  parser->header_state = h_matching_content_length;
+                  break;
+                default:
+                  parser->header_state = h_general;
+                  break;
+              }
+              break;
+
+            /* connection */
+
+            case h_matching_connection:
+              parser->index++;
+              if (parser->index > sizeof(CONNECTION)-1
+                  || c != CONNECTION[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(CONNECTION)-2) {
+                parser->header_state = h_connection;
+              }
+              break;
+
+            /* proxy-connection */
+
+            case h_matching_proxy_connection:
+              parser->index++;
+              if (parser->index > sizeof(PROXY_CONNECTION)-1
+                  || c != PROXY_CONNECTION[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
+                parser->header_state = h_connection;
+              }
+              break;
+
+            /* content-length */
+
+            case h_matching_content_length:
+              parser->index++;
+              if (parser->index > sizeof(CONTENT_LENGTH)-1
+                  || c != CONTENT_LENGTH[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+                parser->header_state = h_content_length;
+              }
+              break;
+
+            /* transfer-encoding */
+
+            case h_matching_transfer_encoding:
+              parser->index++;
+              if (parser->index > sizeof(TRANSFER_ENCODING)-1
+                  || c != TRANSFER_ENCODING[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
+                parser->header_state = h_transfer_encoding;
+              }
+              break;
+
+            /* upgrade */
+
+            case h_matching_upgrade:
+              parser->index++;
+              if (parser->index > sizeof(UPGRADE)-1
+                  || c != UPGRADE[parser->index]) {
+                parser->header_state = h_general;
+              } else if (parser->index == sizeof(UPGRADE)-2) {
+                parser->header_state = h_upgrade;
+              }
+              break;
+
+            case h_connection:
+            case h_content_length:
+            case h_transfer_encoding:
+            case h_upgrade:
+              if (ch != ' ') parser->header_state = h_general;
+              break;
+
+            default:
+              assert(0 && "Unknown header_state");
+              break;
+          }
+        }
+
+        COUNT_HEADER_SIZE(p - start);
+
+        if (p == data + len) {
+          --p;
+          break;
+        }
+
+        if (ch == ':') {
+          UPDATE_STATE(s_header_value_discard_ws);
+          CALLBACK_DATA(header_field);
+          break;
+        }
+
+        SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
+        goto error;
+      }
+
+      case s_header_value_discard_ws:
+        if (ch == ' ' || ch == '\t') break;
+
+        if (ch == CR) {
+          UPDATE_STATE(s_header_value_discard_ws_almost_done);
+          break;
+        }
+
+        if (ch == LF) {
+          UPDATE_STATE(s_header_value_discard_lws);
+          break;
+        }
+
+        /* FALLTHROUGH */
+
+      case s_header_value_start:
+      {
+        MARK(header_value);
+
+        UPDATE_STATE(s_header_value);
+        parser->index = 0;
+
+        c = LOWER(ch);
+
+        switch (parser->header_state) {
+          case h_upgrade:
+            parser->flags |= F_UPGRADE;
+            parser->header_state = h_general;
+            break;
+
+          case h_transfer_encoding:
+            /* looking for 'Transfer-Encoding: chunked' */
+            if ('c' == c) {
+              parser->header_state = h_matching_transfer_encoding_chunked;
+            } else {
+              parser->header_state = h_general;
+            }
+            break;
+
+          case h_content_length:
+            if (UNLIKELY(!IS_NUM(ch))) {
+              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+              goto error;
+            }
+
+            parser->content_length = ch - '0';
+            break;
+
+          case h_connection:
+            /* looking for 'Connection: keep-alive' */
+            if (c == 'k') {
+              parser->header_state = h_matching_connection_keep_alive;
+            /* looking for 'Connection: close' */
+            } else if (c == 'c') {
+              parser->header_state = h_matching_connection_close;
+            } else if (c == 'u') {
+              parser->header_state = h_matching_connection_upgrade;
+            } else {
+              parser->header_state = h_matching_connection_token;
+            }
+            break;
+
+          /* Multi-value `Connection` header */
+          case h_matching_connection_token_start:
+            break;
+
+          default:
+            parser->header_state = h_general;
+            break;
+        }
+        break;
+      }
+
+      case s_header_value:
+      {
+        const char* start = p;
+        enum header_states h_state = parser->header_state;
+        for (; p != data + len; p++) {
+          ch = *p;
+          if (ch == CR) {
+            UPDATE_STATE(s_header_almost_done);
+            parser->header_state = h_state;
+            CALLBACK_DATA(header_value);
+            break;
+          }
+
+          if (ch == LF) {
+            UPDATE_STATE(s_header_almost_done);
+            COUNT_HEADER_SIZE(p - start);
+            parser->header_state = h_state;
+            CALLBACK_DATA_NOADVANCE(header_value);
+            REEXECUTE();
+          }
+
+          c = LOWER(ch);
+
+          switch (h_state) {
+            case h_general:
+            {
+              const char* p_cr;
+              const char* p_lf;
+              size_t limit = data + len - p;
+
+              limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
+
+              p_cr = memchr(p, CR, limit);
+              p_lf = memchr(p, LF, limit);
+              if (p_cr != NULL) {
+                if (p_lf != NULL && p_cr >= p_lf)
+                  p = p_lf;
+                else
+                  p = p_cr;
+              } else if (UNLIKELY(p_lf != NULL)) {
+                p = p_lf;
+              } else {
+                p = data + len;
+              }
+              --p;
+
+              break;
+            }
+
+            case h_connection:
+            case h_transfer_encoding:
+              assert(0 && "Shouldn't get here.");
+              break;
+
+            case h_content_length:
+            {
+              uint64_t t;
+
+              if (ch == ' ') break;
+
+              if (UNLIKELY(!IS_NUM(ch))) {
+                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+                parser->header_state = h_state;
+                goto error;
+              }
+
+              t = parser->content_length;
+              t *= 10;
+              t += ch - '0';
+
+              /* Overflow? Test against a conservative limit for simplicity. */
+              if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {
+                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+                parser->header_state = h_state;
+                goto error;
+              }
+
+              parser->content_length = t;
+              break;
+            }
+
+            /* Transfer-Encoding: chunked */
+            case h_matching_transfer_encoding_chunked:
+              parser->index++;
+              if (parser->index > sizeof(CHUNKED)-1
+                  || c != CHUNKED[parser->index]) {
+                h_state = h_general;
+              } else if (parser->index == sizeof(CHUNKED)-2) {
+                h_state = h_transfer_encoding_chunked;
+              }
+              break;
+
+            case h_matching_connection_token_start:
+              /* looking for 'Connection: keep-alive' */
+              if (c == 'k') {
+                h_state = h_matching_connection_keep_alive;
+              /* looking for 'Connection: close' */
+              } else if (c == 'c') {
+                h_state = h_matching_connection_close;
+              } else if (c == 'u') {
+                h_state = h_matching_connection_upgrade;
+              } else if (STRICT_TOKEN(c)) {
+                h_state = h_matching_connection_token;
+              } else {
+                h_state = h_general;
+              }
+              break;
+
+            /* looking for 'Connection: keep-alive' */
+            case h_matching_connection_keep_alive:
+              parser->index++;
+              if (parser->index > sizeof(KEEP_ALIVE)-1
+                  || c != KEEP_ALIVE[parser->index]) {
+                h_state = h_matching_connection_token;
+              } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
+                h_state = h_connection_keep_alive;
+              }
+              break;
+
+            /* looking for 'Connection: close' */
+            case h_matching_connection_close:
+              parser->index++;
+              if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
+                h_state = h_matching_connection_token;
+              } else if (parser->index == sizeof(CLOSE)-2) {
+                h_state = h_connection_close;
+              }
+              break;
+
+            /* looking for 'Connection: upgrade' */
+            case h_matching_connection_upgrade:
+              parser->index++;
+              if (parser->index > sizeof(UPGRADE) - 1 ||
+                  c != UPGRADE[parser->index]) {
+                h_state = h_matching_connection_token;
+              } else if (parser->index == sizeof(UPGRADE)-2) {
+                h_state = h_connection_upgrade;
+              }
+              break;
+
+            case h_matching_connection_token:
+              if (ch == ',') {
+                h_state = h_matching_connection_token_start;
+                parser->index = 0;
+              }
+              break;
+
+            case h_transfer_encoding_chunked:
+              if (ch != ' ') h_state = h_general;
+              break;
+
+            case h_connection_keep_alive:
+            case h_connection_close:
+            case h_connection_upgrade:
+              if (ch == ',') {
+                if (h_state == h_connection_keep_alive) {
+                  parser->flags |= F_CONNECTION_KEEP_ALIVE;
+                } else if (h_state == h_connection_close) {
+                  parser->flags |= F_CONNECTION_CLOSE;
+                } else if (h_state == h_connection_upgrade) {
+                  parser->flags |= F_CONNECTION_UPGRADE;
+                }
+                h_state = h_matching_connection_token_start;
+                parser->index = 0;
+              } else if (ch != ' ') {
+                h_state = h_matching_connection_token;
+              }
+              break;
+
+            default:
+              UPDATE_STATE(s_header_value);
+              h_state = h_general;
+              break;
+          }
+        }
+        parser->header_state = h_state;
+
+        COUNT_HEADER_SIZE(p - start);
+
+        if (p == data + len)
+          --p;
+        break;
+      }
+
+      case s_header_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+
+        UPDATE_STATE(s_header_value_lws);
+        break;
+      }
+
+      case s_header_value_lws:
+      {
+        if (ch == ' ' || ch == '\t') {
+          UPDATE_STATE(s_header_value_start);
+          REEXECUTE();
+        }
+
+        /* finished the header */
+        switch (parser->header_state) {
+          case h_connection_keep_alive:
+            parser->flags |= F_CONNECTION_KEEP_ALIVE;
+            break;
+          case h_connection_close:
+            parser->flags |= F_CONNECTION_CLOSE;
+            break;
+          case h_transfer_encoding_chunked:
+            parser->flags |= F_CHUNKED;
+            break;
+          case h_connection_upgrade:
+            parser->flags |= F_CONNECTION_UPGRADE;
+            break;
+          default:
+            break;
+        }
+
+        UPDATE_STATE(s_header_field_start);
+        REEXECUTE();
+      }
+
+      case s_header_value_discard_ws_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+        UPDATE_STATE(s_header_value_discard_lws);
+        break;
+      }
+
+      case s_header_value_discard_lws:
+      {
+        if (ch == ' ' || ch == '\t') {
+          UPDATE_STATE(s_header_value_discard_ws);
+          break;
+        } else {
+          switch (parser->header_state) {
+            case h_connection_keep_alive:
+              parser->flags |= F_CONNECTION_KEEP_ALIVE;
+              break;
+            case h_connection_close:
+              parser->flags |= F_CONNECTION_CLOSE;
+              break;
+            case h_connection_upgrade:
+              parser->flags |= F_CONNECTION_UPGRADE;
+              break;
+            case h_transfer_encoding_chunked:
+              parser->flags |= F_CHUNKED;
+              break;
+            default:
+              break;
+          }
+
+          /* header value was empty */
+          MARK(header_value);
+          UPDATE_STATE(s_header_field_start);
+          CALLBACK_DATA_NOADVANCE(header_value);
+          REEXECUTE();
+        }
+      }
+
+      case s_headers_almost_done:
+      {
+        STRICT_CHECK(ch != LF);
+
+        if (parser->flags & F_TRAILING) {
+          /* End of a chunked request */
+          UPDATE_STATE(NEW_MESSAGE());
+          CALLBACK_NOTIFY(message_complete);
+          break;
+        }
+
+        UPDATE_STATE(s_headers_done);
+
+        /* Set this here so that on_headers_complete() callbacks can see it */
+        parser->upgrade =
+          ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) ==
+           (F_UPGRADE | F_CONNECTION_UPGRADE) ||
+           parser->method == HTTP_CONNECT);
+
+        /* Here we call the headers_complete callback. This is somewhat
+         * different than other callbacks because if the user returns 1, we
+         * will interpret that as saying that this message has no body. This
+         * is needed for the annoying case of recieving a response to a HEAD
+         * request.
+         *
+         * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+         * we have to simulate it by handling a change in errno below.
+         */
+        if (settings->on_headers_complete) {
+          switch (settings->on_headers_complete(parser)) {
+            case 0:
+              break;
+
+            case 1:
+              parser->flags |= F_SKIPBODY;
+              break;
+
+            default:
+              SET_ERRNO(HPE_CB_headers_complete);
+              RETURN(p - data); /* Error */
+          }
+        }
+
+        if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+          RETURN(p - data);
+        }
+
+        REEXECUTE();
+      }
+
+      case s_headers_done:
+      {
+        STRICT_CHECK(ch != LF);
+
+        parser->nread = 0;
+
+        /* Exit, the rest of the connect is in a different protocol. */
+        if (parser->upgrade) {
+          UPDATE_STATE(NEW_MESSAGE());
+          CALLBACK_NOTIFY(message_complete);
+          RETURN((p - data) + 1);
+        }
+
+        if (parser->flags & F_SKIPBODY) {
+          UPDATE_STATE(NEW_MESSAGE());
+          CALLBACK_NOTIFY(message_complete);
+        } else if (parser->flags & F_CHUNKED) {
+          /* chunked encoding - ignore Content-Length header */
+          UPDATE_STATE(s_chunk_size_start);
+        } else {
+          if (parser->content_length == 0) {
+            /* Content-Length header given but zero: Content-Length: 0\r\n */
+            UPDATE_STATE(NEW_MESSAGE());
+            CALLBACK_NOTIFY(message_complete);
+          } else if (parser->content_length != ULLONG_MAX) {
+            /* Content-Length header given and non-zero */
+            UPDATE_STATE(s_body_identity);
+          } else {
+            if (parser->type == HTTP_REQUEST ||
+                !http_message_needs_eof(parser)) {
+              /* Assume content-length 0 - read the next */
+              UPDATE_STATE(NEW_MESSAGE());
+              CALLBACK_NOTIFY(message_complete);
+            } else {
+              /* Read body until EOF */
+              UPDATE_STATE(s_body_identity_eof);
+            }
+          }
+        }
+
+        break;
+      }
+
+      case s_body_identity:
+      {
+        uint64_t to_read = MIN(parser->content_length,
+                               (uint64_t) ((data + len) - p));
+
+        assert(parser->content_length != 0
+            && parser->content_length != ULLONG_MAX);
+
+        /* The difference between advancing content_length and p is because
+         * the latter will automaticaly advance on the next loop iteration.
+         * Further, if content_length ends up at 0, we want to see the last
+         * byte again for our message complete callback.
+         */
+        MARK(body);
+        parser->content_length -= to_read;
+        p += to_read - 1;
+
+        if (parser->content_length == 0) {
+          UPDATE_STATE(s_message_done);
+
+          /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
+           *
+           * The alternative to doing this is to wait for the next byte to
+           * trigger the data callback, just as in every other case. The
+           * problem with this is that this makes it difficult for the test
+           * harness to distinguish between complete-on-EOF and
+           * complete-on-length. It's not clear that this distinction is
+           * important for applications, but let's keep it for now.
+           */
+          CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+          REEXECUTE();
+        }
+
+        break;
+      }
+
+      /* read until EOF */
+      case s_body_identity_eof:
+        MARK(body);
+        p = data + len - 1;
+
+        break;
+
+      case s_message_done:
+        UPDATE_STATE(NEW_MESSAGE());
+        CALLBACK_NOTIFY(message_complete);
+        break;
+
+      case s_chunk_size_start:
+      {
+        assert(parser->nread == 1);
+        assert(parser->flags & F_CHUNKED);
+
+        unhex_val = unhex[(unsigned char)ch];
+        if (UNLIKELY(unhex_val == -1)) {
+          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+          goto error;
+        }
+
+        parser->content_length = unhex_val;
+        UPDATE_STATE(s_chunk_size);
+        break;
+      }
+
+      case s_chunk_size:
+      {
+        uint64_t t;
+
+        assert(parser->flags & F_CHUNKED);
+
+        if (ch == CR) {
+          UPDATE_STATE(s_chunk_size_almost_done);
+          break;
+        }
+
+        unhex_val = unhex[(unsigned char)ch];
+
+        if (unhex_val == -1) {
+          if (ch == ';' || ch == ' ') {
+            UPDATE_STATE(s_chunk_parameters);
+            break;
+          }
+
+          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
+          goto error;
+        }
+
+        t = parser->content_length;
+        t *= 16;
+        t += unhex_val;
+
+        /* Overflow? Test against a conservative limit for simplicity. */
+        if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
+          SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+          goto error;
+        }
+
+        parser->content_length = t;
+        break;
+      }
+
+      case s_chunk_parameters:
+      {
+        assert(parser->flags & F_CHUNKED);
+        /* just ignore this shit. TODO check for overflow */
+        if (ch == CR) {
+          UPDATE_STATE(s_chunk_size_almost_done);
+          break;
+        }
+        break;
+      }
+
+      case s_chunk_size_almost_done:
+      {
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != LF);
+
+        parser->nread = 0;
+
+        if (parser->content_length == 0) {
+          parser->flags |= F_TRAILING;
+          UPDATE_STATE(s_header_field_start);
+        } else {
+          UPDATE_STATE(s_chunk_data);
+        }
+        break;
+      }
+
+      case s_chunk_data:
+      {
+        uint64_t to_read = MIN(parser->content_length,
+                               (uint64_t) ((data + len) - p));
+
+        assert(parser->flags & F_CHUNKED);
+        assert(parser->content_length != 0
+            && parser->content_length != ULLONG_MAX);
+
+        /* See the explanation in s_body_identity for why the content
+         * length and data pointers are managed this way.
+         */
+        MARK(body);
+        parser->content_length -= to_read;
+        p += to_read - 1;
+
+        if (parser->content_length == 0) {
+          UPDATE_STATE(s_chunk_data_almost_done);
+        }
+
+        break;
+      }
+
+      case s_chunk_data_almost_done:
+        assert(parser->flags & F_CHUNKED);
+        assert(parser->content_length == 0);
+        STRICT_CHECK(ch != CR);
+        UPDATE_STATE(s_chunk_data_done);
+        CALLBACK_DATA(body);
+        break;
+
+      case s_chunk_data_done:
+        assert(parser->flags & F_CHUNKED);
+        STRICT_CHECK(ch != LF);
+        parser->nread = 0;
+        UPDATE_STATE(s_chunk_size_start);
+        break;
+
+      default:
+        assert(0 && "unhandled state");
+        SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
+        goto error;
+    }
+  }
+
+  /* Run callbacks for any marks that we have leftover after we ran our of
+   * bytes. There should be at most one of these set, so it's OK to invoke
+   * them in series (unset marks will not result in callbacks).
+   *
+   * We use the NOADVANCE() variety of callbacks here because 'p' has already
+   * overflowed 'data' and this allows us to correct for the off-by-one that
+   * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
+   * value that's in-bounds).
+   */
+
+  assert(((header_field_mark ? 1 : 0) +
+          (header_value_mark ? 1 : 0) +
+          (url_mark ? 1 : 0)  +
+          (body_mark ? 1 : 0) +
+          (status_mark ? 1 : 0)) <= 1);
+
+  CALLBACK_DATA_NOADVANCE(header_field);
+  CALLBACK_DATA_NOADVANCE(header_value);
+  CALLBACK_DATA_NOADVANCE(url);
+  CALLBACK_DATA_NOADVANCE(body);
+  CALLBACK_DATA_NOADVANCE(status);
+
+  RETURN(len);
+
+error:
+  if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
+    SET_ERRNO(HPE_UNKNOWN);
+  }
+
+  RETURN(p - data);
+}
+
+
+/* Does the parser need to see an EOF to find the end of the message? */
+int
+http_message_needs_eof (const http_parser *parser)
+{
+  if (parser->type == HTTP_REQUEST) {
+    return 0;
+  }
+
+  /* See RFC 2616 section 4.4 */
+  if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+      parser->status_code == 204 ||     /* No Content */
+      parser->status_code == 304 ||     /* Not Modified */
+      parser->flags & F_SKIPBODY) {     /* response to a HEAD request */
+    return 0;
+  }
+
+  if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
+    return 0;
+  }
+
+  return 1;
+}
+
+
+int
+http_should_keep_alive (const http_parser *parser)
+{
+  if (parser->http_major > 0 && parser->http_minor > 0) {
+    /* HTTP/1.1 */
+    if (parser->flags & F_CONNECTION_CLOSE) {
+      return 0;
+    }
+  } else {
+    /* HTTP/1.0 or earlier */
+    if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
+      return 0;
+    }
+  }
+
+  return !http_message_needs_eof(parser);
+}
+
+
+const char *
+http_method_str (enum http_method m)
+{
+  return ELEM_AT(method_strings, m, "<unknown>");
+}
+
+
+void
+http_parser_init (http_parser *parser, enum http_parser_type t)
+{
+  void *data = parser->data; /* preserve application data */
+  memset(parser, 0, sizeof(*parser));
+  parser->data = data;
+  parser->type = t;
+  parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
+  parser->http_errno = HPE_OK;
+}
+
+const char *
+http_errno_name(enum http_errno err) {
+  assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+  return http_strerror_tab[err].name;
+}
+
+const char *
+http_errno_description(enum http_errno err) {
+  assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
+  return http_strerror_tab[err].description;
+}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+  switch(s) {
+    case s_http_userinfo:
+    case s_http_userinfo_start:
+      if (ch == '@') {
+        return s_http_host_start;
+      }
+
+      if (IS_USERINFO_CHAR(ch)) {
+        return s_http_userinfo;
+      }
+      break;
+
+    case s_http_host_start:
+      if (ch == '[') {
+        return s_http_host_v6_start;
+      }
+
+      if (IS_HOST_CHAR(ch)) {
+        return s_http_host;
+      }
+
+      break;
+
+    case s_http_host:
+      if (IS_HOST_CHAR(ch)) {
+        return s_http_host;
+      }
+
+    /* FALLTHROUGH */
+    case s_http_host_v6_end:
+      if (ch == ':') {
+        return s_http_host_port_start;
+      }
+
+      break;
+
+    case s_http_host_v6:
+      if (ch == ']') {
+        return s_http_host_v6_end;
+      }
+
+    /* FALLTHROUGH */
+    case s_http_host_v6_start:
+      if (IS_HEX(ch) || ch == ':' || ch == '.') {
+        return s_http_host_v6;
+      }
+
+      break;
+
+    case s_http_host_port:
+    case s_http_host_port_start:
+      if (IS_NUM(ch)) {
+        return s_http_host_port;
+      }
+
+      break;
+
+    default:
+      break;
+  }
+  return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+  enum http_host_state s;
+
+  const char *p;
+  size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+  u->field_data[UF_HOST].len = 0;
+
+  s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+  for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+    enum http_host_state new_s = http_parse_host_char(s, *p);
+
+    if (new_s == s_http_host_dead) {
+      return 1;
+    }
+
+    switch(new_s) {
+      case s_http_host:
+        if (s != s_http_host) {
+          u->field_data[UF_HOST].off = p - buf;
+        }
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_v6:
+        if (s != s_http_host_v6) {
+          u->field_data[UF_HOST].off = p - buf;
+        }
+        u->field_data[UF_HOST].len++;
+        break;
+
+      case s_http_host_port:
+        if (s != s_http_host_port) {
+          u->field_data[UF_PORT].off = p - buf;
+          u->field_data[UF_PORT].len = 0;
+          u->field_set |= (1 << UF_PORT);
+        }
+        u->field_data[UF_PORT].len++;
+        break;
+
+      case s_http_userinfo:
+        if (s != s_http_userinfo) {
+          u->field_data[UF_USERINFO].off = p - buf ;
+          u->field_data[UF_USERINFO].len = 0;
+          u->field_set |= (1 << UF_USERINFO);
+        }
+        u->field_data[UF_USERINFO].len++;
+        break;
+
+      default:
+        break;
+    }
+    s = new_s;
+  }
+
+  /* Make sure we don't end somewhere unexpected */
+  switch (s) {
+    case s_http_host_start:
+    case s_http_host_v6_start:
+    case s_http_host_v6:
+    case s_http_host_port_start:
+    case s_http_userinfo:
+    case s_http_userinfo_start:
+      return 1;
+    default:
+      break;
+  }
+
+  return 0;
+}
+
+int
+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
+                      struct http_parser_url *u)
+{
+  enum state s;
+  const char *p;
+  enum http_parser_url_fields uf, old_uf;
+  int found_at = 0;
+
+  u->port = u->field_set = 0;
+  s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+  old_uf = UF_MAX;
+
+  for (p = buf; p < buf + buflen; p++) {
+    s = parse_url_char(s, *p);
+
+    /* Figure out the next field that we're operating on */
+    switch (s) {
+      case s_dead:
+        return 1;
+
+      /* Skip delimeters */
+      case s_req_schema_slash:
+      case s_req_schema_slash_slash:
+      case s_req_server_start:
+      case s_req_query_string_start:
+      case s_req_fragment_start:
+        continue;
+
+      case s_req_schema:
+        uf = UF_SCHEMA;
+        break;
+
+      case s_req_server_with_at:
+        found_at = 1;
+
+      /* FALLTROUGH */
+      case s_req_server:
+        uf = UF_HOST;
+        break;
+
+      case s_req_path:
+        uf = UF_PATH;
+        break;
+
+      case s_req_query_string:
+        uf = UF_QUERY;
+        break;
+
+      case s_req_fragment:
+        uf = UF_FRAGMENT;
+        break;
+
+      default:
+        assert(!"Unexpected state");
+        return 1;
+    }
+
+    /* Nothing's changed; soldier on */
+    if (uf == old_uf) {
+      u->field_data[uf].len++;
+      continue;
+    }
+
+    u->field_data[uf].off = p - buf;
+    u->field_data[uf].len = 1;
+
+    u->field_set |= (1 << uf);
+    old_uf = uf;
+  }
+
+  /* host must be present if there is a schema */
+  /* parsing http:///toto will fail */
+  if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
+    if (http_parse_host(buf, u, found_at) != 0) {
+      return 1;
+    }
+  }
+
+  /* CONNECT requests can only contain "hostname:port" */
+  if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+    return 1;
+  }
+
+  if (u->field_set & (1 << UF_PORT)) {
+    /* Don't bother with endp; we've already validated the string */
+    unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
+
+    /* Ports have a max value of 2^16 */
+    if (v > 0xffff) {
+      return 1;
+    }
+
+    u->port = (uint16_t) v;
+  }
+
+  return 0;
+}
+
+void
+http_parser_pause(http_parser *parser, int paused) {
+  /* Users should only be pausing/unpausing a parser that is not in an error
+   * state. In non-debug builds, there's not much that we can do about this
+   * other than ignore it.
+   */
+  if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
+      HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+    SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
+  } else {
+    assert(0 && "Attempting to pause parser in error state");
+  }
+}
+
+int
+http_body_is_final(const struct http_parser *parser) {
+    return parser->state == s_message_done;
+}
+
+unsigned long
+http_parser_version(void) {
+  return HTTP_PARSER_VERSION_MAJOR * 0x10000 |
+         HTTP_PARSER_VERSION_MINOR * 0x00100 |
+         HTTP_PARSER_VERSION_PATCH * 0x00001;
+}
diff --git a/third-party/http-parser/http_parser.gyp b/third-party/http-parser/http_parser.gyp
new file mode 100644 (file)
index 0000000..ef34eca
--- /dev/null
@@ -0,0 +1,111 @@
+# This file is used with the GYP meta build system.
+# http://code.google.com/p/gyp/
+# To build try this:
+#   svn co http://gyp.googlecode.com/svn/trunk gyp
+#   ./gyp/gyp -f make --depth=`pwd` http_parser.gyp 
+#   ./out/Debug/test 
+{
+  'target_defaults': {
+    'default_configuration': 'Debug',
+    'configurations': {
+      # TODO: hoist these out and put them somewhere common, because
+      #       RuntimeLibrary MUST MATCH across the entire project
+      'Debug': {
+        'defines': [ 'DEBUG', '_DEBUG' ],
+        'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ],
+        'msvs_settings': {
+          'VCCLCompilerTool': {
+            'RuntimeLibrary': 1, # static debug
+          },
+        },
+      },
+      'Release': {
+        'defines': [ 'NDEBUG' ],
+        'cflags': [ '-Wall', '-Wextra', '-O3' ],
+        'msvs_settings': {
+          'VCCLCompilerTool': {
+            'RuntimeLibrary': 0, # static release
+          },
+        },
+      }
+    },
+    'msvs_settings': {
+      'VCCLCompilerTool': {
+      },
+      'VCLibrarianTool': {
+      },
+      'VCLinkerTool': {
+        'GenerateDebugInformation': 'true',
+      },
+    },
+    'conditions': [
+      ['OS == "win"', {
+        'defines': [
+          'WIN32'
+        ],
+      }]
+    ],
+  },
+
+  'targets': [
+    {
+      'target_name': 'http_parser',
+      'type': 'static_library',
+      'include_dirs': [ '.' ],
+      'direct_dependent_settings': {
+        'defines': [ 'HTTP_PARSER_STRICT=0' ],
+        'include_dirs': [ '.' ],
+      },
+      'defines': [ 'HTTP_PARSER_STRICT=0' ],
+      'sources': [ './http_parser.c', ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCCLCompilerTool': {
+              # Compile as C++. http_parser.c is actually C99, but C++ is
+              # close enough in this case.
+              'CompileAs': 2,
+            },
+          },
+        }]
+      ],
+    },
+
+    {
+      'target_name': 'http_parser_strict',
+      'type': 'static_library',
+      'include_dirs': [ '.' ],
+      'direct_dependent_settings': {
+        'defines': [ 'HTTP_PARSER_STRICT=1' ],
+        'include_dirs': [ '.' ],
+      },
+      'defines': [ 'HTTP_PARSER_STRICT=1' ],
+      'sources': [ './http_parser.c', ],
+      'conditions': [
+        ['OS=="win"', {
+          'msvs_settings': {
+            'VCCLCompilerTool': {
+              # Compile as C++. http_parser.c is actually C99, but C++ is
+              # close enough in this case.
+              'CompileAs': 2,
+            },
+          },
+        }]
+      ],
+    },
+
+    {
+      'target_name': 'test-nonstrict',
+      'type': 'executable',
+      'dependencies': [ 'http_parser' ],
+      'sources': [ 'test.c' ]
+    },
+
+    {
+      'target_name': 'test-strict',
+      'type': 'executable',
+      'dependencies': [ 'http_parser_strict' ],
+      'sources': [ 'test.c' ]
+    }
+  ]
+}
diff --git a/third-party/http-parser/http_parser.h b/third-party/http-parser/http_parser.h
new file mode 100644 (file)
index 0000000..936cddb
--- /dev/null
@@ -0,0 +1,330 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef http_parser_h
+#define http_parser_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Also update SONAME in the Makefile whenever you change these. */
+#define HTTP_PARSER_VERSION_MAJOR 2
+#define HTTP_PARSER_VERSION_MINOR 3
+#define HTTP_PARSER_VERSION_PATCH 0
+
+#include <sys/types.h>
+#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
+#include <BaseTsd.h>
+#include <stddef.h>
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+# define HTTP_PARSER_STRICT 1
+#endif
+
+/* Maximium header size allowed. If the macro is not defined
+ * before including this header then the default is used. To
+ * change the maximum header size, define the macro in the build
+ * environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
+ * the effective limit on the size of the header, define the macro
+ * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
+ */
+#ifndef HTTP_MAX_HEADER_SIZE
+# define HTTP_MAX_HEADER_SIZE (80*1024)
+#endif
+
+typedef struct http_parser http_parser;
+typedef struct http_parser_settings http_parser_settings;
+
+
+/* Callbacks should return non-zero to indicate an error. The parser will
+ * then halt execution.
+ *
+ * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
+ * returning '1' from on_headers_complete will tell the parser that it
+ * should not expect a body. This is used when receiving a response to a
+ * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
+ * chunked' headers that indicate the presence of a body.
+ *
+ * http_data_cb does not return data chunks. It will be called arbitrarily
+ * many times for each string. E.G. you might get 10 callbacks for "on_url"
+ * each providing just a few characters more data.
+ */
+typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
+typedef int (*http_cb) (http_parser*);
+
+
+/* Request Methods */
+#define HTTP_METHOD_MAP(XX)         \
+  XX(0,  DELETE,      DELETE)       \
+  XX(1,  GET,         GET)          \
+  XX(2,  HEAD,        HEAD)         \
+  XX(3,  POST,        POST)         \
+  XX(4,  PUT,         PUT)          \
+  /* pathological */                \
+  XX(5,  CONNECT,     CONNECT)      \
+  XX(6,  OPTIONS,     OPTIONS)      \
+  XX(7,  TRACE,       TRACE)        \
+  /* webdav */                      \
+  XX(8,  COPY,        COPY)         \
+  XX(9,  LOCK,        LOCK)         \
+  XX(10, MKCOL,       MKCOL)        \
+  XX(11, MOVE,        MOVE)         \
+  XX(12, PROPFIND,    PROPFIND)     \
+  XX(13, PROPPATCH,   PROPPATCH)    \
+  XX(14, SEARCH,      SEARCH)       \
+  XX(15, UNLOCK,      UNLOCK)       \
+  /* subversion */                  \
+  XX(16, REPORT,      REPORT)       \
+  XX(17, MKACTIVITY,  MKACTIVITY)   \
+  XX(18, CHECKOUT,    CHECKOUT)     \
+  XX(19, MERGE,       MERGE)        \
+  /* upnp */                        \
+  XX(20, MSEARCH,     M-SEARCH)     \
+  XX(21, NOTIFY,      NOTIFY)       \
+  XX(22, SUBSCRIBE,   SUBSCRIBE)    \
+  XX(23, UNSUBSCRIBE, UNSUBSCRIBE)  \
+  /* RFC-5789 */                    \
+  XX(24, PATCH,       PATCH)        \
+  XX(25, PURGE,       PURGE)        \
+  /* CalDAV */                      \
+  XX(26, MKCALENDAR,  MKCALENDAR)   \
+
+enum http_method
+  {
+#define XX(num, name, string) HTTP_##name = num,
+  HTTP_METHOD_MAP(XX)
+#undef XX
+  };
+
+
+enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
+
+
+/* Flag values for http_parser.flags field */
+enum flags
+  { F_CHUNKED               = 1 << 0
+  , F_CONNECTION_KEEP_ALIVE = 1 << 1
+  , F_CONNECTION_CLOSE      = 1 << 2
+  , F_CONNECTION_UPGRADE    = 1 << 3
+  , F_TRAILING              = 1 << 4
+  , F_UPGRADE               = 1 << 5
+  , F_SKIPBODY              = 1 << 6
+  };
+
+
+/* Map for errno-related constants
+ * 
+ * The provided argument should be a macro that takes 2 arguments.
+ */
+#define HTTP_ERRNO_MAP(XX)                                           \
+  /* No error */                                                     \
+  XX(OK, "success")                                                  \
+                                                                     \
+  /* Callback-related errors */                                      \
+  XX(CB_message_begin, "the on_message_begin callback failed")       \
+  XX(CB_url, "the on_url callback failed")                           \
+  XX(CB_header_field, "the on_header_field callback failed")         \
+  XX(CB_header_value, "the on_header_value callback failed")         \
+  XX(CB_headers_complete, "the on_headers_complete callback failed") \
+  XX(CB_body, "the on_body callback failed")                         \
+  XX(CB_message_complete, "the on_message_complete callback failed") \
+  XX(CB_status, "the on_status callback failed")                     \
+                                                                     \
+  /* Parsing-related errors */                                       \
+  XX(INVALID_EOF_STATE, "stream ended at an unexpected time")        \
+  XX(HEADER_OVERFLOW,                                                \
+     "too many header bytes seen; overflow detected")                \
+  XX(CLOSED_CONNECTION,                                              \
+     "data received after completed connection: close message")      \
+  XX(INVALID_VERSION, "invalid HTTP version")                        \
+  XX(INVALID_STATUS, "invalid HTTP status code")                     \
+  XX(INVALID_METHOD, "invalid HTTP method")                          \
+  XX(INVALID_URL, "invalid URL")                                     \
+  XX(INVALID_HOST, "invalid host")                                   \
+  XX(INVALID_PORT, "invalid port")                                   \
+  XX(INVALID_PATH, "invalid path")                                   \
+  XX(INVALID_QUERY_STRING, "invalid query string")                   \
+  XX(INVALID_FRAGMENT, "invalid fragment")                           \
+  XX(LF_EXPECTED, "LF character expected")                           \
+  XX(INVALID_HEADER_TOKEN, "invalid character in header")            \
+  XX(INVALID_CONTENT_LENGTH,                                         \
+     "invalid character in content-length header")                   \
+  XX(INVALID_CHUNK_SIZE,                                             \
+     "invalid character in chunk size header")                       \
+  XX(INVALID_CONSTANT, "invalid constant string")                    \
+  XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
+  XX(STRICT, "strict mode assertion failed")                         \
+  XX(PAUSED, "parser is paused")                                     \
+  XX(UNKNOWN, "an unknown error occurred")
+
+
+/* Define HPE_* values for each errno value above */
+#define HTTP_ERRNO_GEN(n, s) HPE_##n,
+enum http_errno {
+  HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
+};
+#undef HTTP_ERRNO_GEN
+
+
+/* Get an http_errno value from an http_parser */
+#define HTTP_PARSER_ERRNO(p)            ((enum http_errno) (p)->http_errno)
+
+
+struct http_parser {
+  /** PRIVATE **/
+  unsigned int type : 2;         /* enum http_parser_type */
+  unsigned int flags : 6;        /* F_* values from 'flags' enum; semi-public */
+  unsigned int state : 8;        /* enum state from http_parser.c */
+  unsigned int header_state : 8; /* enum header_state from http_parser.c */
+  unsigned int index : 8;        /* index into current matcher */
+
+  uint32_t nread;          /* # bytes read in various scenarios */
+  uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
+
+  /** READ-ONLY **/
+  unsigned short http_major;
+  unsigned short http_minor;
+  unsigned int status_code : 16; /* responses only */
+  unsigned int method : 8;       /* requests only */
+  unsigned int http_errno : 7;
+
+  /* 1 = Upgrade header was present and the parser has exited because of that.
+   * 0 = No upgrade header present.
+   * Should be checked when http_parser_execute() returns in addition to
+   * error checking.
+   */
+  unsigned int upgrade : 1;
+
+  /** PUBLIC **/
+  void *data; /* A pointer to get hook to the "connection" or "socket" object */
+};
+
+
+struct http_parser_settings {
+  http_cb      on_message_begin;
+  http_data_cb on_url;
+  http_data_cb on_status;
+  http_data_cb on_header_field;
+  http_data_cb on_header_value;
+  http_cb      on_headers_complete;
+  http_data_cb on_body;
+  http_cb      on_message_complete;
+};
+
+
+enum http_parser_url_fields
+  { UF_SCHEMA           = 0
+  , UF_HOST             = 1
+  , UF_PORT             = 2
+  , UF_PATH             = 3
+  , UF_QUERY            = 4
+  , UF_FRAGMENT         = 5
+  , UF_USERINFO         = 6
+  , UF_MAX              = 7
+  };
+
+
+/* Result structure for http_parser_parse_url().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+struct http_parser_url {
+  uint16_t field_set;           /* Bitmask of (1 << UF_*) values */
+  uint16_t port;                /* Converted UF_PORT string */
+
+  struct {
+    uint16_t off;               /* Offset into buffer in which field starts */
+    uint16_t len;               /* Length of run in buffer */
+  } field_data[UF_MAX];
+};
+
+
+/* Returns the library version. Bits 16-23 contain the major version number,
+ * bits 8-15 the minor version number and bits 0-7 the patch level.
+ * Usage example:
+ *
+ *   unsigned long version = http_parser_version();
+ *   unsigned major = (version >> 16) & 255;
+ *   unsigned minor = (version >> 8) & 255;
+ *   unsigned patch = version & 255;
+ *   printf("http_parser v%u.%u.%u\n", major, minor, patch);
+ */
+unsigned long http_parser_version(void);
+
+void http_parser_init(http_parser *parser, enum http_parser_type type);
+
+
+/* Executes the parser. Returns number of parsed bytes. Sets
+ * `parser->http_errno` on error. */
+size_t http_parser_execute(http_parser *parser,
+                           const http_parser_settings *settings,
+                           const char *data,
+                           size_t len);
+
+
+/* If http_should_keep_alive() in the on_headers_complete or
+ * on_message_complete callback returns 0, then this should be
+ * the last message on the connection.
+ * If you are the server, respond with the "Connection: close" header.
+ * If you are the client, close the connection.
+ */
+int http_should_keep_alive(const http_parser *parser);
+
+/* Returns a string version of the HTTP method. */
+const char *http_method_str(enum http_method m);
+
+/* Return a string name of the given error */
+const char *http_errno_name(enum http_errno err);
+
+/* Return a string description of the given error */
+const char *http_errno_description(enum http_errno err);
+
+/* Parse a URL; return nonzero on failure */
+int http_parser_parse_url(const char *buf, size_t buflen,
+                          int is_connect,
+                          struct http_parser_url *u);
+
+/* Pause or un-pause the parser; a nonzero value pauses */
+void http_parser_pause(http_parser *parser, int paused);
+
+/* Checks if this is the final chunk of the body. */
+int http_body_is_final(const http_parser *parser);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/third-party/http-parser/test.c b/third-party/http-parser/test.c
new file mode 100644 (file)
index 0000000..6c45d59
--- /dev/null
@@ -0,0 +1,3615 @@
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include "http_parser.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h> /* rand */
+#include <string.h>
+#include <stdarg.h>
+
+#if defined(__APPLE__)
+# undef strlcat
+# undef strlncpy
+# undef strlcpy
+#endif  /* defined(__APPLE__) */
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+
+#define MAX_HEADERS 13
+#define MAX_ELEMENT_SIZE 2048
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+static http_parser *parser;
+
+struct message {
+  const char *name; // for debugging purposes
+  const char *raw;
+  enum http_parser_type type;
+  enum http_method method;
+  int status_code;
+  char response_status[MAX_ELEMENT_SIZE];
+  char request_path[MAX_ELEMENT_SIZE];
+  char request_url[MAX_ELEMENT_SIZE];
+  char fragment[MAX_ELEMENT_SIZE];
+  char query_string[MAX_ELEMENT_SIZE];
+  char body[MAX_ELEMENT_SIZE];
+  size_t body_size;
+  const char *host;
+  const char *userinfo;
+  uint16_t port;
+  int num_headers;
+  enum { NONE=0, FIELD, VALUE } last_header_element;
+  char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
+  int should_keep_alive;
+
+  const char *upgrade; // upgraded body
+
+  unsigned short http_major;
+  unsigned short http_minor;
+
+  int message_begin_cb_called;
+  int headers_complete_cb_called;
+  int message_complete_cb_called;
+  int message_complete_on_eof;
+  int body_is_final;
+};
+
+static int currently_parsing_eof;
+
+static struct message messages[5];
+static int num_messages;
+static http_parser_settings *current_pause_parser;
+
+/* * R E Q U E S T S * */
+const struct message requests[] =
+#define CURL_GET 0
+{ {.name= "curl get"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /test HTTP/1.1\r\n"
+         "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
+         "Host: 0.0.0.0=5000\r\n"
+         "Accept: */*\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/test"
+  ,.request_url= "/test"
+  ,.num_headers= 3
+  ,.headers=
+    { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
+    , { "Host", "0.0.0.0=5000" }
+    , { "Accept", "*/*" }
+    }
+  ,.body= ""
+  }
+
+#define FIREFOX_GET 1
+, {.name= "firefox get"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /favicon.ico HTTP/1.1\r\n"
+         "Host: 0.0.0.0=5000\r\n"
+         "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
+         "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+         "Accept-Language: en-us,en;q=0.5\r\n"
+         "Accept-Encoding: gzip,deflate\r\n"
+         "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
+         "Keep-Alive: 300\r\n"
+         "Connection: keep-alive\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/favicon.ico"
+  ,.request_url= "/favicon.ico"
+  ,.num_headers= 8
+  ,.headers=
+    { { "Host", "0.0.0.0=5000" }
+    , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
+    , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
+    , { "Accept-Language", "en-us,en;q=0.5" }
+    , { "Accept-Encoding", "gzip,deflate" }
+    , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" }
+    , { "Keep-Alive", "300" }
+    , { "Connection", "keep-alive" }
+    }
+  ,.body= ""
+  }
+
+#define DUMBFUCK 2
+, {.name= "dumbfuck"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
+         "aaaaaaaaaaaaa:++++++++++\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/dumbfuck"
+  ,.request_url= "/dumbfuck"
+  ,.num_headers= 1
+  ,.headers=
+    { { "aaaaaaaaaaaaa",  "++++++++++" }
+    }
+  ,.body= ""
+  }
+
+#define FRAGMENT_IN_URI 3
+, {.name= "fragment in url"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= "page=1"
+  ,.fragment= "posts-17408"
+  ,.request_path= "/forums/1/topics/2375"
+  /* XXX request url does include fragment? */
+  ,.request_url= "/forums/1/topics/2375?page=1#posts-17408"
+  ,.num_headers= 0
+  ,.body= ""
+  }
+
+#define GET_NO_HEADERS_NO_BODY 4
+, {.name= "get no headers no body"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE /* would need Connection: close */
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/get_no_headers_no_body/world"
+  ,.request_url= "/get_no_headers_no_body/world"
+  ,.num_headers= 0
+  ,.body= ""
+  }
+
+#define GET_ONE_HEADER_NO_BODY 5
+, {.name= "get one header no body"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n"
+         "Accept: */*\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE /* would need Connection: close */
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/get_one_header_no_body"
+  ,.request_url= "/get_one_header_no_body"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Accept" , "*/*" }
+    }
+  ,.body= ""
+  }
+
+#define GET_FUNKY_CONTENT_LENGTH 6
+, {.name= "get funky content length body hello"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
+         "conTENT-Length: 5\r\n"
+         "\r\n"
+         "HELLO"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/get_funky_content_length_body_hello"
+  ,.request_url= "/get_funky_content_length_body_hello"
+  ,.num_headers= 1
+  ,.headers=
+    { { "conTENT-Length" , "5" }
+    }
+  ,.body= "HELLO"
+  }
+
+#define POST_IDENTITY_BODY_WORLD 7
+, {.name= "post identity body world"
+  ,.type= HTTP_REQUEST
+  ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
+         "Accept: */*\r\n"
+         "Transfer-Encoding: identity\r\n"
+         "Content-Length: 5\r\n"
+         "\r\n"
+         "World"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= "q=search"
+  ,.fragment= "hey"
+  ,.request_path= "/post_identity_body_world"
+  ,.request_url= "/post_identity_body_world?q=search#hey"
+  ,.num_headers= 3
+  ,.headers=
+    { { "Accept", "*/*" }
+    , { "Transfer-Encoding", "identity" }
+    , { "Content-Length", "5" }
+    }
+  ,.body= "World"
+  }
+
+#define POST_CHUNKED_ALL_YOUR_BASE 8
+, {.name= "post - chunked body: all your base are belong to us"
+  ,.type= HTTP_REQUEST
+  ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "1e\r\nall your base are belong to us\r\n"
+         "0\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/post_chunked_all_your_base"
+  ,.request_url= "/post_chunked_all_your_base"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Transfer-Encoding" , "chunked" }
+    }
+  ,.body= "all your base are belong to us"
+  }
+
+#define TWO_CHUNKS_MULT_ZERO_END 9
+, {.name= "two chunks ; triple zero ending"
+  ,.type= HTTP_REQUEST
+  ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "5\r\nhello\r\n"
+         "6\r\n world\r\n"
+         "000\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/two_chunks_mult_zero_end"
+  ,.request_url= "/two_chunks_mult_zero_end"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Transfer-Encoding", "chunked" }
+    }
+  ,.body= "hello world"
+  }
+
+#define CHUNKED_W_TRAILING_HEADERS 10
+, {.name= "chunked with trailing headers. blech."
+  ,.type= HTTP_REQUEST
+  ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "5\r\nhello\r\n"
+         "6\r\n world\r\n"
+         "0\r\n"
+         "Vary: *\r\n"
+         "Content-Type: text/plain\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/chunked_w_trailing_headers"
+  ,.request_url= "/chunked_w_trailing_headers"
+  ,.num_headers= 3
+  ,.headers=
+    { { "Transfer-Encoding",  "chunked" }
+    , { "Vary", "*" }
+    , { "Content-Type", "text/plain" }
+    }
+  ,.body= "hello world"
+  }
+
+#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
+, {.name= "with bullshit after the length"
+  ,.type= HTTP_REQUEST
+  ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
+         "6; blahblah; blah\r\n world\r\n"
+         "0\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/chunked_w_bullshit_after_length"
+  ,.request_url= "/chunked_w_bullshit_after_length"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Transfer-Encoding", "chunked" }
+    }
+  ,.body= "hello world"
+  }
+
+#define WITH_QUOTES 12
+, {.name= "with quotes"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= "foo=\"bar\""
+  ,.fragment= ""
+  ,.request_path= "/with_\"stupid\"_quotes"
+  ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\""
+  ,.num_headers= 0
+  ,.headers= { }
+  ,.body= ""
+  }
+
+#define APACHEBENCH_GET 13
+/* The server receiving this request SHOULD NOT wait for EOF
+ * to know that content-length == 0.
+ * How to represent this in a unit test? message_complete_on_eof
+ * Compare with NO_CONTENT_LENGTH_RESPONSE.
+ */
+, {.name = "apachebench get"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /test HTTP/1.0\r\n"
+         "Host: 0.0.0.0:5000\r\n"
+         "User-Agent: ApacheBench/2.3\r\n"
+         "Accept: */*\r\n\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/test"
+  ,.request_url= "/test"
+  ,.num_headers= 3
+  ,.headers= { { "Host", "0.0.0.0:5000" }
+             , { "User-Agent", "ApacheBench/2.3" }
+             , { "Accept", "*/*" }
+             }
+  ,.body= ""
+  }
+
+#define QUERY_URL_WITH_QUESTION_MARK_GET 14
+/* Some clients include '?' characters in query strings.
+ */
+, {.name = "query url with question mark"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= "foo=bar?baz"
+  ,.fragment= ""
+  ,.request_path= "/test.cgi"
+  ,.request_url= "/test.cgi?foo=bar?baz"
+  ,.num_headers= 0
+  ,.headers= {}
+  ,.body= ""
+  }
+
+#define PREFIX_NEWLINE_GET 15
+/* Some clients, especially after a POST in a keep-alive connection,
+ * will send an extra CRLF before the next request
+ */
+, {.name = "newline prefix get"
+  ,.type= HTTP_REQUEST
+  ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/test"
+  ,.request_url= "/test"
+  ,.num_headers= 0
+  ,.headers= { }
+  ,.body= ""
+  }
+
+#define UPGRADE_REQUEST 16
+, {.name = "upgrade request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /demo HTTP/1.1\r\n"
+         "Host: example.com\r\n"
+         "Connection: Upgrade\r\n"
+         "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n"
+         "Sec-WebSocket-Protocol: sample\r\n"
+         "Upgrade: WebSocket\r\n"
+         "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n"
+         "Origin: http://example.com\r\n"
+         "\r\n"
+         "Hot diggity dogg"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/demo"
+  ,.request_url= "/demo"
+  ,.num_headers= 7
+  ,.upgrade="Hot diggity dogg"
+  ,.headers= { { "Host", "example.com" }
+             , { "Connection", "Upgrade" }
+             , { "Sec-WebSocket-Key2", "12998 5 Y3 1  .P00" }
+             , { "Sec-WebSocket-Protocol", "sample" }
+             , { "Upgrade", "WebSocket" }
+             , { "Sec-WebSocket-Key1", "4 @1  46546xW%0l 1 5" }
+             , { "Origin", "http://example.com" }
+             }
+  ,.body= ""
+  }
+
+#define CONNECT_REQUEST 17
+, {.name = "connect request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n"
+         "User-agent: Mozilla/1.1N\r\n"
+         "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+         "\r\n"
+         "some data\r\n"
+         "and yet even more data"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.method= HTTP_CONNECT
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= ""
+  ,.request_url= "0-home0.netscape.com:443"
+  ,.num_headers= 2
+  ,.upgrade="some data\r\nand yet even more data"
+  ,.headers= { { "User-agent", "Mozilla/1.1N" }
+             , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+             }
+  ,.body= ""
+  }
+
+#define REPORT_REQ 18
+, {.name= "report request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "REPORT /test HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_REPORT
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/test"
+  ,.request_url= "/test"
+  ,.num_headers= 0
+  ,.headers= {}
+  ,.body= ""
+  }
+
+#define NO_HTTP_VERSION 19
+, {.name= "request with no http version"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 0
+  ,.http_minor= 9
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/"
+  ,.request_url= "/"
+  ,.num_headers= 0
+  ,.headers= {}
+  ,.body= ""
+  }
+
+#define MSEARCH_REQ 20
+, {.name= "m-search request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "M-SEARCH * HTTP/1.1\r\n"
+         "HOST: 239.255.255.250:1900\r\n"
+         "MAN: \"ssdp:discover\"\r\n"
+         "ST: \"ssdp:all\"\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_MSEARCH
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "*"
+  ,.request_url= "*"
+  ,.num_headers= 3
+  ,.headers= { { "HOST", "239.255.255.250:1900" }
+             , { "MAN", "\"ssdp:discover\"" }
+             , { "ST", "\"ssdp:all\"" }
+             }
+  ,.body= ""
+  }
+
+#define LINE_FOLDING_IN_HEADER 21
+, {.name= "line folding in header value"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET / HTTP/1.1\r\n"
+         "Line1:   abc\r\n"
+         "\tdef\r\n"
+         " ghi\r\n"
+         "\t\tjkl\r\n"
+         "  mno \r\n"
+         "\t \tqrs\r\n"
+         "Line2: \t line2\t\r\n"
+         "Line3:\r\n"
+         " line3\r\n"
+         "Line4: \r\n"
+         " \r\n"
+         "Connection:\r\n"
+         " close\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/"
+  ,.request_url= "/"
+  ,.num_headers= 5
+  ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl  mno \t \tqrs" }
+             , { "Line2", "line2\t" }
+             , { "Line3", "line3" }
+             , { "Line4", "" }
+             , { "Connection", "close" },
+             }
+  ,.body= ""
+  }
+
+
+#define QUERY_TERMINATED_HOST 22
+, {.name= "host terminated by a query string"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= "hail=all"
+  ,.fragment= ""
+  ,.request_path= ""
+  ,.request_url= "http://hypnotoad.org?hail=all"
+  ,.host= "hypnotoad.org"
+  ,.num_headers= 0
+  ,.headers= { }
+  ,.body= ""
+  }
+
+#define QUERY_TERMINATED_HOSTPORT 23
+, {.name= "host:port terminated by a query string"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= "hail=all"
+  ,.fragment= ""
+  ,.request_path= ""
+  ,.request_url= "http://hypnotoad.org:1234?hail=all"
+  ,.host= "hypnotoad.org"
+  ,.port= 1234
+  ,.num_headers= 0
+  ,.headers= { }
+  ,.body= ""
+  }
+
+#define SPACE_TERMINATED_HOSTPORT 24
+, {.name= "host:port terminated by a space"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= ""
+  ,.request_url= "http://hypnotoad.org:1234"
+  ,.host= "hypnotoad.org"
+  ,.port= 1234
+  ,.num_headers= 0
+  ,.headers= { }
+  ,.body= ""
+  }
+
+#define PATCH_REQ 25
+, {.name = "PATCH request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "PATCH /file.txt HTTP/1.1\r\n"
+         "Host: www.example.com\r\n"
+         "Content-Type: application/example\r\n"
+         "If-Match: \"e0023aa4e\"\r\n"
+         "Content-Length: 10\r\n"
+         "\r\n"
+         "cccccccccc"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_PATCH
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/file.txt"
+  ,.request_url= "/file.txt"
+  ,.num_headers= 4
+  ,.headers= { { "Host", "www.example.com" }
+             , { "Content-Type", "application/example" }
+             , { "If-Match", "\"e0023aa4e\"" }
+             , { "Content-Length", "10" }
+             }
+  ,.body= "cccccccccc"
+  }
+
+#define CONNECT_CAPS_REQUEST 26
+, {.name = "connect caps request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n"
+         "User-agent: Mozilla/1.1N\r\n"
+         "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.method= HTTP_CONNECT
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= ""
+  ,.request_url= "HOME0.NETSCAPE.COM:443"
+  ,.num_headers= 2
+  ,.upgrade=""
+  ,.headers= { { "User-agent", "Mozilla/1.1N" }
+             , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+             }
+  ,.body= ""
+  }
+
+#if !HTTP_PARSER_STRICT
+#define UTF8_PATH_REQ 27
+, {.name= "utf-8 path request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n"
+         "Host: github.com\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= "q=1"
+  ,.fragment= "narf"
+  ,.request_path= "/δ¶/δt/pope"
+  ,.request_url= "/δ¶/δt/pope?q=1#narf"
+  ,.num_headers= 1
+  ,.headers= { {"Host", "github.com" }
+             }
+  ,.body= ""
+  }
+
+#define HOSTNAME_UNDERSCORE 28
+, {.name = "hostname underscore"
+  ,.type= HTTP_REQUEST
+  ,.raw= "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n"
+         "User-agent: Mozilla/1.1N\r\n"
+         "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.method= HTTP_CONNECT
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= ""
+  ,.request_url= "home_0.netscape.com:443"
+  ,.num_headers= 2
+  ,.upgrade=""
+  ,.headers= { { "User-agent", "Mozilla/1.1N" }
+             , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
+             }
+  ,.body= ""
+  }
+#endif  /* !HTTP_PARSER_STRICT */
+
+/* see https://github.com/ry/http-parser/issues/47 */
+#define EAT_TRAILING_CRLF_NO_CONNECTION_CLOSE 29
+, {.name = "eat CRLF between requests, no \"Connection: close\" header"
+  ,.raw= "POST / HTTP/1.1\r\n"
+         "Host: www.example.com\r\n"
+         "Content-Type: application/x-www-form-urlencoded\r\n"
+         "Content-Length: 4\r\n"
+         "\r\n"
+         "q=42\r\n" /* note the trailing CRLF */
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/"
+  ,.request_url= "/"
+  ,.num_headers= 3
+  ,.upgrade= 0
+  ,.headers= { { "Host", "www.example.com" }
+             , { "Content-Type", "application/x-www-form-urlencoded" }
+             , { "Content-Length", "4" }
+             }
+  ,.body= "q=42"
+  }
+
+/* see https://github.com/ry/http-parser/issues/47 */
+#define EAT_TRAILING_CRLF_WITH_CONNECTION_CLOSE 30
+, {.name = "eat CRLF between requests even if \"Connection: close\" is set"
+  ,.raw= "POST / HTTP/1.1\r\n"
+         "Host: www.example.com\r\n"
+         "Content-Type: application/x-www-form-urlencoded\r\n"
+         "Content-Length: 4\r\n"
+         "Connection: close\r\n"
+         "\r\n"
+         "q=42\r\n" /* note the trailing CRLF */
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE /* input buffer isn't empty when on_message_complete is called */
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_POST
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/"
+  ,.request_url= "/"
+  ,.num_headers= 4
+  ,.upgrade= 0
+  ,.headers= { { "Host", "www.example.com" }
+             , { "Content-Type", "application/x-www-form-urlencoded" }
+             , { "Content-Length", "4" }
+             , { "Connection", "close" }
+             }
+  ,.body= "q=42"
+  }
+
+#define PURGE_REQ 31
+, {.name = "PURGE request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "PURGE /file.txt HTTP/1.1\r\n"
+         "Host: www.example.com\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_PURGE
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/file.txt"
+  ,.request_url= "/file.txt"
+  ,.num_headers= 1
+  ,.headers= { { "Host", "www.example.com" } }
+  ,.body= ""
+  }
+
+#define SEARCH_REQ 32
+, {.name = "SEARCH request"
+  ,.type= HTTP_REQUEST
+  ,.raw= "SEARCH / HTTP/1.1\r\n"
+         "Host: www.example.com\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_SEARCH
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/"
+  ,.request_url= "/"
+  ,.num_headers= 1
+  ,.headers= { { "Host", "www.example.com" } }
+  ,.body= ""
+  }
+
+#define PROXY_WITH_BASIC_AUTH 33
+, {.name= "host:port and basic_auth"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.fragment= ""
+  ,.request_path= "/toto"
+  ,.request_url= "http://a%12:b!&*$@hypnotoad.org:1234/toto"
+  ,.host= "hypnotoad.org"
+  ,.userinfo= "a%12:b!&*$"
+  ,.port= 1234
+  ,.num_headers= 0
+  ,.headers= { }
+  ,.body= ""
+  }
+
+#define LINE_FOLDING_IN_HEADER_WITH_LF 34
+, {.name= "line folding in header value"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET / HTTP/1.1\n"
+         "Line1:   abc\n"
+         "\tdef\n"
+         " ghi\n"
+         "\t\tjkl\n"
+         "  mno \n"
+         "\t \tqrs\n"
+         "Line2: \t line2\t\n"
+         "Line3:\n"
+         " line3\n"
+         "Line4: \n"
+         " \n"
+         "Connection:\n"
+         " close\n"
+         "\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/"
+  ,.request_url= "/"
+  ,.num_headers= 5
+  ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl  mno \t \tqrs" }
+             , { "Line2", "line2\t" }
+             , { "Line3", "line3" }
+             , { "Line4", "" }
+             , { "Connection", "close" },
+             }
+  ,.body= ""
+  }
+
+#define CONNECTION_MULTI 35
+, {.name = "multiple connection header values with folding"
+  ,.type= HTTP_REQUEST
+  ,.raw= "GET /demo HTTP/1.1\r\n"
+         "Host: example.com\r\n"
+         "Connection: Something,\r\n"
+         " Upgrade, ,Keep-Alive\r\n"
+         "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n"
+         "Sec-WebSocket-Protocol: sample\r\n"
+         "Upgrade: WebSocket\r\n"
+         "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n"
+         "Origin: http://example.com\r\n"
+         "\r\n"
+         "Hot diggity dogg"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.method= HTTP_GET
+  ,.query_string= ""
+  ,.fragment= ""
+  ,.request_path= "/demo"
+  ,.request_url= "/demo"
+  ,.num_headers= 7
+  ,.upgrade="Hot diggity dogg"
+  ,.headers= { { "Host", "example.com" }
+             , { "Connection", "Something, Upgrade, ,Keep-Alive" }
+             , { "Sec-WebSocket-Key2", "12998 5 Y3 1  .P00" }
+             , { "Sec-WebSocket-Protocol", "sample" }
+             , { "Upgrade", "WebSocket" }
+             , { "Sec-WebSocket-Key1", "4 @1  46546xW%0l 1 5" }
+             , { "Origin", "http://example.com" }
+             }
+  ,.body= ""
+  }
+
+
+, {.name= NULL } /* sentinel */
+};
+
+/* * R E S P O N S E S * */
+const struct message responses[] =
+#define GOOGLE_301 0
+{ {.name= "google 301"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
+         "Location: http://www.google.com/\r\n"
+         "Content-Type: text/html; charset=UTF-8\r\n"
+         "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n"
+         "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n"
+         "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */
+         "Cache-Control: public, max-age=2592000\r\n"
+         "Server: gws\r\n"
+         "Content-Length:  219  \r\n"
+         "\r\n"
+         "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
+         "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
+         "<H1>301 Moved</H1>\n"
+         "The document has moved\n"
+         "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
+         "</BODY></HTML>\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 301
+  ,.response_status= "Moved Permanently"
+  ,.num_headers= 8
+  ,.headers=
+    { { "Location", "http://www.google.com/" }
+    , { "Content-Type", "text/html; charset=UTF-8" }
+    , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
+    , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" }
+    , { "X-$PrototypeBI-Version", "1.6.0.3" }
+    , { "Cache-Control", "public, max-age=2592000" }
+    , { "Server", "gws" }
+    , { "Content-Length", "219  " }
+    }
+  ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
+          "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
+          "<H1>301 Moved</H1>\n"
+          "The document has moved\n"
+          "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
+          "</BODY></HTML>\r\n"
+  }
+
+#define NO_CONTENT_LENGTH_RESPONSE 1
+/* The client should wait for the server's EOF. That is, when content-length
+ * is not specified, and "Connection: close", the end of body is specified
+ * by the EOF.
+ * Compare with APACHEBENCH_GET
+ */
+, {.name= "no content-length response"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
+         "Server: Apache\r\n"
+         "X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
+         "Content-Type: text/xml; charset=utf-8\r\n"
+         "Connection: close\r\n"
+         "\r\n"
+         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+         "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+         "  <SOAP-ENV:Body>\n"
+         "    <SOAP-ENV:Fault>\n"
+         "       <faultcode>SOAP-ENV:Client</faultcode>\n"
+         "       <faultstring>Client Error</faultstring>\n"
+         "    </SOAP-ENV:Fault>\n"
+         "  </SOAP-ENV:Body>\n"
+         "</SOAP-ENV:Envelope>"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 5
+  ,.headers=
+    { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
+    , { "Server", "Apache" }
+    , { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
+    , { "Content-Type", "text/xml; charset=utf-8" }
+    , { "Connection", "close" }
+    }
+  ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+          "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+          "  <SOAP-ENV:Body>\n"
+          "    <SOAP-ENV:Fault>\n"
+          "       <faultcode>SOAP-ENV:Client</faultcode>\n"
+          "       <faultstring>Client Error</faultstring>\n"
+          "    </SOAP-ENV:Fault>\n"
+          "  </SOAP-ENV:Body>\n"
+          "</SOAP-ENV:Envelope>"
+  }
+
+#define NO_HEADERS_NO_BODY_404 2
+, {.name= "404 no headers no body"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 404
+  ,.response_status= "Not Found"
+  ,.num_headers= 0
+  ,.headers= {}
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#define NO_REASON_PHRASE 3
+, {.name= "301 no response phrase"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 301\r\n\r\n"
+  ,.should_keep_alive = FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 301
+  ,.response_status= ""
+  ,.num_headers= 0
+  ,.headers= {}
+  ,.body= ""
+  }
+
+#define TRAILING_SPACE_ON_CHUNKED_BODY 4
+, {.name="200 trailing space on chunked body"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Content-Type: text/plain\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "25  \r\n"
+         "This is the data in the first chunk\r\n"
+         "\r\n"
+         "1C\r\n"
+         "and this is the second one\r\n"
+         "\r\n"
+         "0  \r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 2
+  ,.headers=
+    { {"Content-Type", "text/plain" }
+    , {"Transfer-Encoding", "chunked" }
+    }
+  ,.body_size = 37+28
+  ,.body =
+         "This is the data in the first chunk\r\n"
+         "and this is the second one\r\n"
+
+  }
+
+#define NO_CARRIAGE_RET 5
+, {.name="no carriage ret"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\n"
+         "Content-Type: text/html; charset=utf-8\n"
+         "Connection: close\n"
+         "\n"
+         "these headers are from http://news.ycombinator.com/"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 2
+  ,.headers=
+    { {"Content-Type", "text/html; charset=utf-8" }
+    , {"Connection", "close" }
+    }
+  ,.body= "these headers are from http://news.ycombinator.com/"
+  }
+
+#define PROXY_CONNECTION 6
+, {.name="proxy connection"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Content-Type: text/html; charset=UTF-8\r\n"
+         "Content-Length: 11\r\n"
+         "Proxy-Connection: close\r\n"
+         "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n"
+         "\r\n"
+         "hello world"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 4
+  ,.headers=
+    { {"Content-Type", "text/html; charset=UTF-8" }
+    , {"Content-Length", "11" }
+    , {"Proxy-Connection", "close" }
+    , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"}
+    }
+  ,.body= "hello world"
+  }
+
+#define UNDERSTORE_HEADER_KEY 7
+  // shown by
+  // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;"
+, {.name="underscore header key"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Server: DCLK-AdSvr\r\n"
+         "Content-Type: text/xml\r\n"
+         "Content-Length: 0\r\n"
+         "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 4
+  ,.headers=
+    { {"Server", "DCLK-AdSvr" }
+    , {"Content-Type", "text/xml" }
+    , {"Content-Length", "0" }
+    , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" }
+    }
+  ,.body= ""
+  }
+
+#define BONJOUR_MADAME_FR 8
+/* The client should not merge two headers fields when the first one doesn't
+ * have a value.
+ */
+, {.name= "bonjourmadame.fr"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.0 301 Moved Permanently\r\n"
+         "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n"
+         "Server: Apache/2.2.3 (Red Hat)\r\n"
+         "Cache-Control: public\r\n"
+         "Pragma: \r\n"
+         "Location: http://www.bonjourmadame.fr/\r\n"
+         "Vary: Accept-Encoding\r\n"
+         "Content-Length: 0\r\n"
+         "Content-Type: text/html; charset=UTF-8\r\n"
+         "Connection: keep-alive\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.status_code= 301
+  ,.response_status= "Moved Permanently"
+  ,.num_headers= 9
+  ,.headers=
+    { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" }
+    , { "Server", "Apache/2.2.3 (Red Hat)" }
+    , { "Cache-Control", "public" }
+    , { "Pragma", "" }
+    , { "Location", "http://www.bonjourmadame.fr/" }
+    , { "Vary",  "Accept-Encoding" }
+    , { "Content-Length", "0" }
+    , { "Content-Type", "text/html; charset=UTF-8" }
+    , { "Connection", "keep-alive" }
+    }
+  ,.body= ""
+  }
+
+#define RES_FIELD_UNDERSCORE 9
+/* Should handle spaces in header fields */
+, {.name= "field underscore"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n"
+         "Server: Apache\r\n"
+         "Cache-Control: no-cache, must-revalidate\r\n"
+         "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+         ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n"
+         "Vary: Accept-Encoding\r\n"
+         "_eep-Alive: timeout=45\r\n" /* semantic value ignored */
+         "_onnection: Keep-Alive\r\n" /* semantic value ignored */
+         "Transfer-Encoding: chunked\r\n"
+         "Content-Type: text/html\r\n"
+         "Connection: close\r\n"
+         "\r\n"
+         "0\r\n\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 11
+  ,.headers=
+    { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" }
+    , { "Server", "Apache" }
+    , { "Cache-Control", "no-cache, must-revalidate" }
+    , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" }
+    , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" }
+    , { "Vary", "Accept-Encoding" }
+    , { "_eep-Alive", "timeout=45" }
+    , { "_onnection", "Keep-Alive" }
+    , { "Transfer-Encoding", "chunked" }
+    , { "Content-Type", "text/html" }
+    , { "Connection", "close" }
+    }
+  ,.body= ""
+  }
+
+#define NON_ASCII_IN_STATUS_LINE 10
+/* Should handle non-ASCII in status line */
+, {.name= "non-ASCII in status line"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n"
+         "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n"
+         "Content-Length: 0\r\n"
+         "Connection: close\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 500
+  ,.response_status= "Oriëntatieprobleem"
+  ,.num_headers= 3
+  ,.headers=
+    { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" }
+    , { "Content-Length", "0" }
+    , { "Connection", "close" }
+    }
+  ,.body= ""
+  }
+
+#define HTTP_VERSION_0_9 11
+/* Should handle HTTP/0.9 */
+, {.name= "http version 0.9"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/0.9 200 OK\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 0
+  ,.http_minor= 9
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 0
+  ,.headers=
+    {}
+  ,.body= ""
+  }
+
+#define NO_CONTENT_LENGTH_NO_TRANSFER_ENCODING_RESPONSE 12
+/* The client should wait for the server's EOF. That is, when neither
+ * content-length nor transfer-encoding is specified, the end of body
+ * is specified by the EOF.
+ */
+, {.name= "neither content-length nor transfer-encoding response"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Content-Type: text/plain\r\n"
+         "\r\n"
+         "hello world"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Content-Type", "text/plain" }
+    }
+  ,.body= "hello world"
+  }
+
+#define NO_BODY_HTTP10_KA_200 13
+, {.name= "HTTP/1.0 with keep-alive and EOF-terminated 200 status"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.0 200 OK\r\n"
+         "Connection: keep-alive\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Connection", "keep-alive" }
+    }
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#define NO_BODY_HTTP10_KA_204 14
+, {.name= "HTTP/1.0 with keep-alive and a 204 status"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.0 204 No content\r\n"
+         "Connection: keep-alive\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 0
+  ,.status_code= 204
+  ,.response_status= "No content"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Connection", "keep-alive" }
+    }
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#define NO_BODY_HTTP11_KA_200 15
+, {.name= "HTTP/1.1 with an EOF-terminated 200 status"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 0
+  ,.headers={}
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#define NO_BODY_HTTP11_KA_204 16
+, {.name= "HTTP/1.1 with a 204 status"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 204 No content\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 204
+  ,.response_status= "No content"
+  ,.num_headers= 0
+  ,.headers={}
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#define NO_BODY_HTTP11_NOKA_204 17
+, {.name= "HTTP/1.1 with a 204 status and keep-alive disabled"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 204 No content\r\n"
+         "Connection: close\r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 204
+  ,.response_status= "No content"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Connection", "close" }
+    }
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#define NO_BODY_HTTP11_KA_CHUNKED_200 18
+, {.name= "HTTP/1.1 with chunked endocing and a 200 response"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "0\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 1
+  ,.headers=
+    { { "Transfer-Encoding", "chunked" }
+    }
+  ,.body_size= 0
+  ,.body= ""
+  }
+
+#if !HTTP_PARSER_STRICT
+#define SPACE_IN_FIELD_RES 19
+/* Should handle spaces in header fields */
+, {.name= "field space"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 OK\r\n"
+         "Server: Microsoft-IIS/6.0\r\n"
+         "X-Powered-By: ASP.NET\r\n"
+         "en-US Content-Type: text/xml\r\n" /* this is the problem */
+         "Content-Type: text/xml\r\n"
+         "Content-Length: 16\r\n"
+         "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n"
+         "Connection: keep-alive\r\n"
+         "\r\n"
+         "<xml>hello</xml>" /* fake body */
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= "OK"
+  ,.num_headers= 7
+  ,.headers=
+    { { "Server",  "Microsoft-IIS/6.0" }
+    , { "X-Powered-By", "ASP.NET" }
+    , { "en-US Content-Type", "text/xml" }
+    , { "Content-Type", "text/xml" }
+    , { "Content-Length", "16" }
+    , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" }
+    , { "Connection", "keep-alive" }
+    }
+  ,.body= "<xml>hello</xml>"
+  }
+#endif /* !HTTP_PARSER_STRICT */
+
+#define AMAZON_COM 20
+, {.name= "amazon.com"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 301 MovedPermanently\r\n"
+         "Date: Wed, 15 May 2013 17:06:33 GMT\r\n"
+         "Server: Server\r\n"
+         "x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n"
+         "p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n"
+         "x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n"
+         "Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n"
+         "Vary: Accept-Encoding,User-Agent\r\n"
+         "Content-Type: text/html; charset=ISO-8859-1\r\n"
+         "Transfer-Encoding: chunked\r\n"
+         "\r\n"
+         "1\r\n"
+         "\n\r\n"
+         "0\r\n"
+         "\r\n"
+  ,.should_keep_alive= TRUE
+  ,.message_complete_on_eof= FALSE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 301
+  ,.response_status= "MovedPermanently"
+  ,.num_headers= 9
+  ,.headers= { { "Date", "Wed, 15 May 2013 17:06:33 GMT" }
+             , { "Server", "Server" }
+             , { "x-amz-id-1", "0GPHKXSJQ826RK7GZEB2" }
+             , { "p3p", "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"" }
+             , { "x-amz-id-2", "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD" }
+             , { "Location", "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846" }
+             , { "Vary", "Accept-Encoding,User-Agent" }
+             , { "Content-Type", "text/html; charset=ISO-8859-1" }
+             , { "Transfer-Encoding", "chunked" }
+             }
+  ,.body= "\n"
+  }
+
+#define EMPTY_REASON_PHRASE_AFTER_SPACE 20
+, {.name= "empty reason phrase after space"
+  ,.type= HTTP_RESPONSE
+  ,.raw= "HTTP/1.1 200 \r\n"
+         "\r\n"
+  ,.should_keep_alive= FALSE
+  ,.message_complete_on_eof= TRUE
+  ,.http_major= 1
+  ,.http_minor= 1
+  ,.status_code= 200
+  ,.response_status= ""
+  ,.num_headers= 0
+  ,.headers= {}
+  ,.body= ""
+  }
+
+, {.name= NULL } /* sentinel */
+};
+
+/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
+ * define it ourselves.
+ */
+size_t
+strnlen(const char *s, size_t maxlen)
+{
+  const char *p;
+
+  p = memchr(s, '\0', maxlen);
+  if (p == NULL)
+    return maxlen;
+
+  return p - s;
+}
+
+size_t
+strlncat(char *dst, size_t len, const char *src, size_t n)
+{
+  size_t slen;
+  size_t dlen;
+  size_t rlen;
+  size_t ncpy;
+
+  slen = strnlen(src, n);
+  dlen = strnlen(dst, len);
+
+  if (dlen < len) {
+    rlen = len - dlen;
+    ncpy = slen < rlen ? slen : (rlen - 1);
+    memcpy(dst + dlen, src, ncpy);
+    dst[dlen + ncpy] = '\0';
+  }
+
+  assert(len > slen + dlen);
+  return slen + dlen;
+}
+
+size_t
+strlcat(char *dst, const char *src, size_t len)
+{
+  return strlncat(dst, len, src, (size_t) -1);
+}
+
+size_t
+strlncpy(char *dst, size_t len, const char *src, size_t n)
+{
+  size_t slen;
+  size_t ncpy;
+
+  slen = strnlen(src, n);
+
+  if (len > 0) {
+    ncpy = slen < len ? slen : (len - 1);
+    memcpy(dst, src, ncpy);
+    dst[ncpy] = '\0';
+  }
+
+  assert(len > slen);
+  return slen;
+}
+
+size_t
+strlcpy(char *dst, const char *src, size_t len)
+{
+  return strlncpy(dst, len, src, (size_t) -1);
+}
+
+int
+request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  strlncat(messages[num_messages].request_url,
+           sizeof(messages[num_messages].request_url),
+           buf,
+           len);
+  return 0;
+}
+
+int
+header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  struct message *m = &messages[num_messages];
+
+  if (m->last_header_element != FIELD)
+    m->num_headers++;
+
+  strlncat(m->headers[m->num_headers-1][0],
+           sizeof(m->headers[m->num_headers-1][0]),
+           buf,
+           len);
+
+  m->last_header_element = FIELD;
+
+  return 0;
+}
+
+int
+header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  struct message *m = &messages[num_messages];
+
+  strlncat(m->headers[m->num_headers-1][1],
+           sizeof(m->headers[m->num_headers-1][1]),
+           buf,
+           len);
+
+  m->last_header_element = VALUE;
+
+  return 0;
+}
+
+void
+check_body_is_final (const http_parser *p)
+{
+  if (messages[num_messages].body_is_final) {
+    fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 "
+                    "on last on_body callback call "
+                    "but it doesn't! ***\n\n");
+    assert(0);
+    abort();
+  }
+  messages[num_messages].body_is_final = http_body_is_final(p);
+}
+
+int
+body_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  strlncat(messages[num_messages].body,
+           sizeof(messages[num_messages].body),
+           buf,
+           len);
+  messages[num_messages].body_size += len;
+  check_body_is_final(p);
+ // printf("body_cb: '%s'\n", requests[num_messages].body);
+  return 0;
+}
+
+int
+count_body_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  assert(buf);
+  messages[num_messages].body_size += len;
+  check_body_is_final(p);
+  return 0;
+}
+
+int
+message_begin_cb (http_parser *p)
+{
+  assert(p == parser);
+  messages[num_messages].message_begin_cb_called = TRUE;
+  return 0;
+}
+
+int
+headers_complete_cb (http_parser *p)
+{
+  assert(p == parser);
+  messages[num_messages].method = parser->method;
+  messages[num_messages].status_code = parser->status_code;
+  messages[num_messages].http_major = parser->http_major;
+  messages[num_messages].http_minor = parser->http_minor;
+  messages[num_messages].headers_complete_cb_called = TRUE;
+  messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
+  return 0;
+}
+
+int
+message_complete_cb (http_parser *p)
+{
+  assert(p == parser);
+  if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
+  {
+    fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
+                    "value in both on_message_complete and on_headers_complete "
+                    "but it doesn't! ***\n\n");
+    assert(0);
+    abort();
+  }
+
+  if (messages[num_messages].body_size &&
+      http_body_is_final(p) &&
+      !messages[num_messages].body_is_final)
+  {
+    fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 "
+                    "on last on_body callback call "
+                    "but it doesn't! ***\n\n");
+    assert(0);
+    abort();
+  }
+
+  messages[num_messages].message_complete_cb_called = TRUE;
+
+  messages[num_messages].message_complete_on_eof = currently_parsing_eof;
+
+  num_messages++;
+  return 0;
+}
+
+int
+response_status_cb (http_parser *p, const char *buf, size_t len)
+{
+  assert(p == parser);
+  strlncat(messages[num_messages].response_status,
+           sizeof(messages[num_messages].response_status),
+           buf,
+           len);
+  return 0;
+}
+
+/* These dontcall_* callbacks exist so that we can verify that when we're
+ * paused, no additional callbacks are invoked */
+int
+dontcall_message_begin_cb (http_parser *p)
+{
+  if (p) { } // gcc
+  fprintf(stderr, "\n\n*** on_message_begin() called on paused parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+  if (p || buf || len) { } // gcc
+  fprintf(stderr, "\n\n*** on_header_field() called on paused parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+  if (p || buf || len) { } // gcc
+  fprintf(stderr, "\n\n*** on_header_value() called on paused parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+  if (p || buf || len) { } // gcc
+  fprintf(stderr, "\n\n*** on_request_url() called on paused parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_body_cb (http_parser *p, const char *buf, size_t len)
+{
+  if (p || buf || len) { } // gcc
+  fprintf(stderr, "\n\n*** on_body_cb() called on paused parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_headers_complete_cb (http_parser *p)
+{
+  if (p) { } // gcc
+  fprintf(stderr, "\n\n*** on_headers_complete() called on paused "
+                  "parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_message_complete_cb (http_parser *p)
+{
+  if (p) { } // gcc
+  fprintf(stderr, "\n\n*** on_message_complete() called on paused "
+                  "parser ***\n\n");
+  abort();
+}
+
+int
+dontcall_response_status_cb (http_parser *p, const char *buf, size_t len)
+{
+  if (p || buf || len) { } // gcc
+  fprintf(stderr, "\n\n*** on_status() called on paused parser ***\n\n");
+  abort();
+}
+
+static http_parser_settings settings_dontcall =
+  {.on_message_begin = dontcall_message_begin_cb
+  ,.on_header_field = dontcall_header_field_cb
+  ,.on_header_value = dontcall_header_value_cb
+  ,.on_url = dontcall_request_url_cb
+  ,.on_status = dontcall_response_status_cb
+  ,.on_body = dontcall_body_cb
+  ,.on_headers_complete = dontcall_headers_complete_cb
+  ,.on_message_complete = dontcall_message_complete_cb
+  };
+
+/* These pause_* callbacks always pause the parser and just invoke the regular
+ * callback that tracks content. Before returning, we overwrite the parser
+ * settings to point to the _dontcall variety so that we can verify that
+ * the pause actually did, you know, pause. */
+int
+pause_message_begin_cb (http_parser *p)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return message_begin_cb(p);
+}
+
+int
+pause_header_field_cb (http_parser *p, const char *buf, size_t len)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return header_field_cb(p, buf, len);
+}
+
+int
+pause_header_value_cb (http_parser *p, const char *buf, size_t len)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return header_value_cb(p, buf, len);
+}
+
+int
+pause_request_url_cb (http_parser *p, const char *buf, size_t len)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return request_url_cb(p, buf, len);
+}
+
+int
+pause_body_cb (http_parser *p, const char *buf, size_t len)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return body_cb(p, buf, len);
+}
+
+int
+pause_headers_complete_cb (http_parser *p)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return headers_complete_cb(p);
+}
+
+int
+pause_message_complete_cb (http_parser *p)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return message_complete_cb(p);
+}
+
+int
+pause_response_status_cb (http_parser *p, const char *buf, size_t len)
+{
+  http_parser_pause(p, 1);
+  *current_pause_parser = settings_dontcall;
+  return response_status_cb(p, buf, len);
+}
+
+static http_parser_settings settings_pause =
+  {.on_message_begin = pause_message_begin_cb
+  ,.on_header_field = pause_header_field_cb
+  ,.on_header_value = pause_header_value_cb
+  ,.on_url = pause_request_url_cb
+  ,.on_status = pause_response_status_cb
+  ,.on_body = pause_body_cb
+  ,.on_headers_complete = pause_headers_complete_cb
+  ,.on_message_complete = pause_message_complete_cb
+  };
+
+static http_parser_settings settings =
+  {.on_message_begin = message_begin_cb
+  ,.on_header_field = header_field_cb
+  ,.on_header_value = header_value_cb
+  ,.on_url = request_url_cb
+  ,.on_status = response_status_cb
+  ,.on_body = body_cb
+  ,.on_headers_complete = headers_complete_cb
+  ,.on_message_complete = message_complete_cb
+  };
+
+static http_parser_settings settings_count_body =
+  {.on_message_begin = message_begin_cb
+  ,.on_header_field = header_field_cb
+  ,.on_header_value = header_value_cb
+  ,.on_url = request_url_cb
+  ,.on_status = response_status_cb
+  ,.on_body = count_body_cb
+  ,.on_headers_complete = headers_complete_cb
+  ,.on_message_complete = message_complete_cb
+  };
+
+static http_parser_settings settings_null =
+  {.on_message_begin = 0
+  ,.on_header_field = 0
+  ,.on_header_value = 0
+  ,.on_url = 0
+  ,.on_status = 0
+  ,.on_body = 0
+  ,.on_headers_complete = 0
+  ,.on_message_complete = 0
+  };
+
+void
+parser_init (enum http_parser_type type)
+{
+  num_messages = 0;
+
+  assert(parser == NULL);
+
+  parser = malloc(sizeof(http_parser));
+
+  http_parser_init(parser, type);
+
+  memset(&messages, 0, sizeof messages);
+
+}
+
+void
+parser_free ()
+{
+  assert(parser);
+  free(parser);
+  parser = NULL;
+}
+
+size_t parse (const char *buf, size_t len)
+{
+  size_t nparsed;
+  currently_parsing_eof = (len == 0);
+  nparsed = http_parser_execute(parser, &settings, buf, len);
+  return nparsed;
+}
+
+size_t parse_count_body (const char *buf, size_t len)
+{
+  size_t nparsed;
+  currently_parsing_eof = (len == 0);
+  nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
+  return nparsed;
+}
+
+size_t parse_pause (const char *buf, size_t len)
+{
+  size_t nparsed;
+  http_parser_settings s = settings_pause;
+
+  currently_parsing_eof = (len == 0);
+  current_pause_parser = &s;
+  nparsed = http_parser_execute(parser, current_pause_parser, buf, len);
+  return nparsed;
+}
+
+static inline int
+check_str_eq (const struct message *m,
+              const char *prop,
+              const char *expected,
+              const char *found) {
+  if ((expected == NULL) != (found == NULL)) {
+    printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+    printf("expected %s\n", (expected == NULL) ? "NULL" : expected);
+    printf("   found %s\n", (found == NULL) ? "NULL" : found);
+    return 0;
+  }
+  if (expected != NULL && 0 != strcmp(expected, found)) {
+    printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+    printf("expected '%s'\n", expected);
+    printf("   found '%s'\n", found);
+    return 0;
+  }
+  return 1;
+}
+
+static inline int
+check_num_eq (const struct message *m,
+              const char *prop,
+              int expected,
+              int found) {
+  if (expected != found) {
+    printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
+    printf("expected %d\n", expected);
+    printf("   found %d\n", found);
+    return 0;
+  }
+  return 1;
+}
+
+#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \
+  if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0
+
+#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \
+  if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0
+
+#define MESSAGE_CHECK_URL_EQ(u, expected, found, prop, fn)           \
+do {                                                                 \
+  char ubuf[256];                                                    \
+                                                                     \
+  if ((u)->field_set & (1 << (fn))) {                                \
+    memcpy(ubuf, (found)->request_url + (u)->field_data[(fn)].off,   \
+      (u)->field_data[(fn)].len);                                    \
+    ubuf[(u)->field_data[(fn)].len] = '\0';                          \
+  } else {                                                           \
+    ubuf[0] = '\0';                                                  \
+  }                                                                  \
+                                                                     \
+  check_str_eq(expected, #prop, expected->prop, ubuf);               \
+} while(0)
+
+int
+message_eq (int index, const struct message *expected)
+{
+  int i;
+  struct message *m = &messages[index];
+
+  MESSAGE_CHECK_NUM_EQ(expected, m, http_major);
+  MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);
+
+  if (expected->type == HTTP_REQUEST) {
+    MESSAGE_CHECK_NUM_EQ(expected, m, method);
+  } else {
+    MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
+    MESSAGE_CHECK_STR_EQ(expected, m, response_status);
+  }
+
+  MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive);
+  MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);
+
+  assert(m->message_begin_cb_called);
+  assert(m->headers_complete_cb_called);
+  assert(m->message_complete_cb_called);
+
+
+  MESSAGE_CHECK_STR_EQ(expected, m, request_url);
+
+  /* Check URL components; we can't do this w/ CONNECT since it doesn't
+   * send us a well-formed URL.
+   */
+  if (*m->request_url && m->method != HTTP_CONNECT) {
+    struct http_parser_url u;
+
+    if (http_parser_parse_url(m->request_url, strlen(m->request_url), 0, &u)) {
+      fprintf(stderr, "\n\n*** failed to parse URL %s ***\n\n",
+        m->request_url);
+      abort();
+    }
+
+    if (expected->host) {
+      MESSAGE_CHECK_URL_EQ(&u, expected, m, host, UF_HOST);
+    }
+
+    if (expected->userinfo) {
+      MESSAGE_CHECK_URL_EQ(&u, expected, m, userinfo, UF_USERINFO);
+    }
+
+    m->port = (u.field_set & (1 << UF_PORT)) ?
+      u.port : 0;
+
+    MESSAGE_CHECK_URL_EQ(&u, expected, m, query_string, UF_QUERY);
+    MESSAGE_CHECK_URL_EQ(&u, expected, m, fragment, UF_FRAGMENT);
+    MESSAGE_CHECK_URL_EQ(&u, expected, m, request_path, UF_PATH);
+    MESSAGE_CHECK_NUM_EQ(expected, m, port);
+  }
+
+  if (expected->body_size) {
+    MESSAGE_CHECK_NUM_EQ(expected, m, body_size);
+  } else {
+    MESSAGE_CHECK_STR_EQ(expected, m, body);
+  }
+
+  MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
+
+  int r;
+  for (i = 0; i < m->num_headers; i++) {
+    r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]);
+    if (!r) return 0;
+    r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]);
+    if (!r) return 0;
+  }
+
+  MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
+
+  return 1;
+}
+
+/* Given a sequence of varargs messages, return the number of them that the
+ * parser should successfully parse, taking into account that upgraded
+ * messages prevent all subsequent messages from being parsed.
+ */
+size_t
+count_parsed_messages(const size_t nmsgs, ...) {
+  size_t i;
+  va_list ap;
+
+  va_start(ap, nmsgs);
+
+  for (i = 0; i < nmsgs; i++) {
+    struct message *m = va_arg(ap, struct message *);
+
+    if (m->upgrade) {
+      va_end(ap);
+      return i + 1;
+    }
+  }
+
+  va_end(ap);
+  return nmsgs;
+}
+
+/* Given a sequence of bytes and the number of these that we were able to
+ * parse, verify that upgrade bodies are correct.
+ */
+void
+upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {
+  va_list ap;
+  size_t i;
+  size_t off = 0;
+  va_start(ap, nmsgs);
+
+  for (i = 0; i < nmsgs; i++) {
+    struct message *m = va_arg(ap, struct message *);
+
+    off += strlen(m->raw);
+
+    if (m->upgrade) {
+      off -= strlen(m->upgrade);
+
+      /* Check the portion of the response after its specified upgrade */
+      if (!check_str_eq(m, "upgrade", body + off, body + nread)) {
+        abort();
+      }
+
+      /* Fix up the response so that message_eq() will verify the beginning
+       * of the upgrade */
+      *(body + nread + strlen(m->upgrade)) = '\0';
+      messages[num_messages -1 ].upgrade = body + nread;
+
+      va_end(ap);
+      return;
+    }
+  }
+
+  va_end(ap);
+  printf("\n\n*** Error: expected a message with upgrade ***\n");
+
+  abort();
+}
+
+static void
+print_error (const char *raw, size_t error_location)
+{
+  fprintf(stderr, "\n*** %s ***\n\n",
+          http_errno_description(HTTP_PARSER_ERRNO(parser)));
+
+  int this_line = 0, char_len = 0;
+  size_t i, j, len = strlen(raw), error_location_line = 0;
+  for (i = 0; i < len; i++) {
+    if (i == error_location) this_line = 1;
+    switch (raw[i]) {
+      case '\r':
+        char_len = 2;
+        fprintf(stderr, "\\r");
+        break;
+
+      case '\n':
+        fprintf(stderr, "\\n\n");
+
+        if (this_line) goto print;
+
+        error_location_line = 0;
+        continue;
+
+      default:
+        char_len = 1;
+        fputc(raw[i], stderr);
+        break;
+    }
+    if (!this_line) error_location_line += char_len;
+  }
+
+  fprintf(stderr, "[eof]\n");
+
+ print:
+  for (j = 0; j < error_location_line; j++) {
+    fputc(' ', stderr);
+  }
+  fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location);
+}
+
+void
+test_preserve_data (void)
+{
+  char my_data[] = "application-specific data";
+  http_parser parser;
+  parser.data = my_data;
+  http_parser_init(&parser, HTTP_REQUEST);
+  if (parser.data != my_data) {
+    printf("\n*** parser.data not preserved accross http_parser_init ***\n\n");
+    abort();
+  }
+}
+
+struct url_test {
+  const char *name;
+  const char *url;
+  int is_connect;
+  struct http_parser_url u;
+  int rv;
+};
+
+const struct url_test url_tests[] =
+{ {.name="proxy request"
+  ,.url="http://hostname/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  7,  8 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 15,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="proxy request with port"
+  ,.url="http://hostname:444/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)
+    ,.port=444
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  7,  8 } /* UF_HOST */
+      ,{ 16,  3 } /* UF_PORT */
+      ,{ 19,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="CONNECT request"
+  ,.url="hostname:443"
+  ,.is_connect=1
+  ,.u=
+    {.field_set=(1 << UF_HOST) | (1 << UF_PORT)
+    ,.port=443
+    ,.field_data=
+      {{  0,  0 } /* UF_SCHEMA */
+      ,{  0,  8 } /* UF_HOST */
+      ,{  9,  3 } /* UF_PORT */
+      ,{  0,  0 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="CONNECT request but not connect"
+  ,.url="hostname:443"
+  ,.is_connect=0
+  ,.rv=1
+  }
+
+, {.name="proxy ipv6 request"
+  ,.url="http://[1:2::3:4]/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  8,  8 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 17,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="proxy ipv6 request with port"
+  ,.url="http://[1:2::3:4]:67/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PORT) | (1 << UF_PATH)
+    ,.port=67
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  8,  8 } /* UF_HOST */
+      ,{ 18,  2 } /* UF_PORT */
+      ,{ 20,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="CONNECT ipv6 address"
+  ,.url="[1:2::3:4]:443"
+  ,.is_connect=1
+  ,.u=
+    {.field_set=(1 << UF_HOST) | (1 << UF_PORT)
+    ,.port=443
+    ,.field_data=
+      {{  0,  0 } /* UF_SCHEMA */
+      ,{  1,  8 } /* UF_HOST */
+      ,{ 11,  3 } /* UF_PORT */
+      ,{  0,  0 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="ipv4 in ipv6 address"
+  ,.url="http://[2001:0000:0000:0000:0000:0000:1.9.1.1]/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set=(1 << UF_SCHEMA) | (1 << UF_HOST) | (1 << UF_PATH)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  8, 37 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 46,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="extra ? in query string"
+  ,.url="http://a.tbcdn.cn/p/fp/2010c/??fp-header-min.css,fp-base-min.css,"
+  "fp-channel-min.css,fp-product-min.css,fp-mall-min.css,fp-category-min.css,"
+  "fp-sub-min.css,fp-gdp4p-min.css,fp-css3-min.css,fp-misc-min.css?t=20101022.css"
+  ,.is_connect=0
+  ,.u=
+    {.field_set=(1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  7, 10 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 17, 12 } /* UF_PATH */
+      ,{ 30,187 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="space URL encoded"
+  ,.url="/toto.html?toto=a%20b"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_PATH) | (1<<UF_QUERY)
+    ,.port=0
+    ,.field_data=
+      {{  0,  0 } /* UF_SCHEMA */
+      ,{  0,  0 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{  0, 10 } /* UF_PATH */
+      ,{ 11, 10 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+
+, {.name="URL fragment"
+  ,.url="/toto.html#titi"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_PATH) | (1<<UF_FRAGMENT)
+    ,.port=0
+    ,.field_data=
+      {{  0,  0 } /* UF_SCHEMA */
+      ,{  0,  0 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{  0, 10 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{ 11,  4 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="complex URL fragment"
+  ,.url="http://www.webmasterworld.com/r.cgi?f=21&d=8405&url="
+    "http://www.example.com/index.html?foo=bar&hello=world#midpage"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_QUERY) |\
+      (1<<UF_FRAGMENT)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  7, 22 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 29,  6 } /* UF_PATH */
+      ,{ 36, 69 } /* UF_QUERY */
+      ,{106,  7 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="complex URL from node js url parser doc"
+  ,.url="http://host.com:8080/p/a/t/h?query=string#hash"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\
+      (1<<UF_QUERY) | (1<<UF_FRAGMENT)
+    ,.port=8080
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  7,  8 } /* UF_HOST */
+      ,{ 16,  4 } /* UF_PORT */
+      ,{ 20,  8 } /* UF_PATH */
+      ,{ 29, 12 } /* UF_QUERY */
+      ,{ 42,  4 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="complex URL with basic auth from node js url parser doc"
+  ,.url="http://a:b@host.com:8080/p/a/t/h?query=string#hash"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PORT) | (1<<UF_PATH) |\
+      (1<<UF_QUERY) | (1<<UF_FRAGMENT) | (1<<UF_USERINFO)
+    ,.port=8080
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{ 11,  8 } /* UF_HOST */
+      ,{ 20,  4 } /* UF_PORT */
+      ,{ 24,  8 } /* UF_PATH */
+      ,{ 33, 12 } /* UF_QUERY */
+      ,{ 46,  4 } /* UF_FRAGMENT */
+      ,{  7,  3 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="double @"
+  ,.url="http://a:b@@hostname:443/"
+  ,.is_connect=0
+  ,.rv=1
+  }
+
+, {.name="proxy empty host"
+  ,.url="http://:443/"
+  ,.is_connect=0
+  ,.rv=1
+  }
+
+, {.name="proxy empty port"
+  ,.url="http://hostname:/"
+  ,.is_connect=0
+  ,.rv=1
+  }
+
+, {.name="CONNECT with basic auth"
+  ,.url="a:b@hostname:443"
+  ,.is_connect=1
+  ,.rv=1
+  }
+
+, {.name="CONNECT empty host"
+  ,.url=":443"
+  ,.is_connect=1
+  ,.rv=1
+  }
+
+, {.name="CONNECT empty port"
+  ,.url="hostname:"
+  ,.is_connect=1
+  ,.rv=1
+  }
+
+, {.name="CONNECT with extra bits"
+  ,.url="hostname:443/"
+  ,.is_connect=1
+  ,.rv=1
+  }
+
+, {.name="space in URL"
+  ,.url="/foo bar/"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy basic auth with space url encoded"
+  ,.url="http://a%20:b@host.com/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{ 14,  8 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 22,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  7,  6 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="carriage return in URL"
+  ,.url="/foo\rbar/"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy double : in URL"
+  ,.url="http://hostname::443/"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy basic auth with double :"
+  ,.url="http://a::b@host.com/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{ 12,  8 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 20,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  7,  4 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="line feed in URL"
+  ,.url="/foo\nbar/"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy empty basic auth"
+  ,.url="http://@hostname/fo"
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{  8,  8 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 16,  3 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+, {.name="proxy line feed in hostname"
+  ,.url="http://host\name/fo"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy % in hostname"
+  ,.url="http://host%name/fo"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy ; in hostname"
+  ,.url="http://host;ame/fo"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy basic auth with unreservedchars"
+  ,.url="http://a!;-_!=+$@host.com/"
+  ,.is_connect=0
+  ,.u=
+    {.field_set= (1<<UF_SCHEMA) | (1<<UF_HOST) | (1<<UF_PATH) | (1<<UF_USERINFO)
+    ,.port=0
+    ,.field_data=
+      {{  0,  4 } /* UF_SCHEMA */
+      ,{ 17,  8 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{ 25,  1 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  7,  9 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="proxy only empty basic auth"
+  ,.url="http://@/fo"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy only basic auth"
+  ,.url="http://toto@/fo"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy emtpy hostname"
+  ,.url="http:///fo"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="proxy = in URL"
+  ,.url="http://host=ame/fo"
+  ,.rv=1 /* s_dead */
+  }
+
+#if HTTP_PARSER_STRICT
+
+, {.name="tab in URL"
+  ,.url="/foo\tbar/"
+  ,.rv=1 /* s_dead */
+  }
+
+, {.name="form feed in URL"
+  ,.url="/foo\fbar/"
+  ,.rv=1 /* s_dead */
+  }
+
+#else /* !HTTP_PARSER_STRICT */
+
+, {.name="tab in URL"
+  ,.url="/foo\tbar/"
+  ,.u=
+    {.field_set=(1 << UF_PATH)
+    ,.field_data=
+      {{  0,  0 } /* UF_SCHEMA */
+      ,{  0,  0 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{  0,  9 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+
+, {.name="form feed in URL"
+  ,.url="/foo\fbar/"
+  ,.u=
+    {.field_set=(1 << UF_PATH)
+    ,.field_data=
+      {{  0,  0 } /* UF_SCHEMA */
+      ,{  0,  0 } /* UF_HOST */
+      ,{  0,  0 } /* UF_PORT */
+      ,{  0,  9 } /* UF_PATH */
+      ,{  0,  0 } /* UF_QUERY */
+      ,{  0,  0 } /* UF_FRAGMENT */
+      ,{  0,  0 } /* UF_USERINFO */
+      }
+    }
+  ,.rv=0
+  }
+#endif
+};
+
+void
+dump_url (const char *url, const struct http_parser_url *u)
+{
+  unsigned int i;
+
+  printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
+  for (i = 0; i < UF_MAX; i++) {
+    if ((u->field_set & (1 << i)) == 0) {
+      printf("\tfield_data[%u]: unset\n", i);
+      continue;
+    }
+
+    printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n\"",
+           i,
+           u->field_data[i].off,
+           u->field_data[i].len,
+           u->field_data[i].len,
+           url + u->field_data[i].off);
+  }
+}
+
+void
+test_parse_url (void)
+{
+  struct http_parser_url u;
+  const struct url_test *test;
+  unsigned int i;
+  int rv;
+
+  for (i = 0; i < (sizeof(url_tests) / sizeof(url_tests[0])); i++) {
+    test = &url_tests[i];
+    memset(&u, 0, sizeof(u));
+
+    rv = http_parser_parse_url(test->url,
+                               strlen(test->url),
+                               test->is_connect,
+                               &u);
+
+    if (test->rv == 0) {
+      if (rv != 0) {
+        printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, "
+               "unexpected rv %d ***\n\n", test->url, test->name, rv);
+        abort();
+      }
+
+      if (memcmp(&u, &test->u, sizeof(u)) != 0) {
+        printf("\n*** http_parser_parse_url(\"%s\") \"%s\" failed ***\n",
+               test->url, test->name);
+
+        printf("target http_parser_url:\n");
+        dump_url(test->url, &test->u);
+        printf("result http_parser_url:\n");
+        dump_url(test->url, &u);
+
+        abort();
+      }
+    } else {
+      /* test->rv != 0 */
+      if (rv == 0) {
+        printf("\n*** http_parser_parse_url(\"%s\") \"%s\" test failed, "
+               "unexpected rv %d ***\n\n", test->url, test->name, rv);
+        abort();
+      }
+    }
+  }
+}
+
+void
+test_method_str (void)
+{
+  assert(0 == strcmp("GET", http_method_str(HTTP_GET)));
+  assert(0 == strcmp("<unknown>", http_method_str(1337)));
+}
+
+void
+test_message (const struct message *message)
+{
+  size_t raw_len = strlen(message->raw);
+  size_t msg1len;
+  for (msg1len = 0; msg1len < raw_len; msg1len++) {
+    parser_init(message->type);
+
+    size_t read;
+    const char *msg1 = message->raw;
+    const char *msg2 = msg1 + msg1len;
+    size_t msg2len = raw_len - msg1len;
+
+    if (msg1len) {
+      read = parse(msg1, msg1len);
+
+      if (message->upgrade && parser->upgrade) {
+        messages[num_messages - 1].upgrade = msg1 + read;
+        goto test;
+      }
+
+      if (read != msg1len) {
+        print_error(msg1, read);
+        abort();
+      }
+    }
+
+
+    read = parse(msg2, msg2len);
+
+    if (message->upgrade && parser->upgrade) {
+      messages[num_messages - 1].upgrade = msg2 + read;
+      goto test;
+    }
+
+    if (read != msg2len) {
+      print_error(msg2, read);
+      abort();
+    }
+
+    read = parse(NULL, 0);
+
+    if (read != 0) {
+      print_error(message->raw, read);
+      abort();
+    }
+
+  test:
+
+    if (num_messages != 1) {
+      printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+      abort();
+    }
+
+    if(!message_eq(0, message)) abort();
+
+    parser_free();
+  }
+}
+
+void
+test_message_count_body (const struct message *message)
+{
+  parser_init(message->type);
+
+  size_t read;
+  size_t l = strlen(message->raw);
+  size_t i, toread;
+  size_t chunk = 4024;
+
+  for (i = 0; i < l; i+= chunk) {
+    toread = MIN(l-i, chunk);
+    read = parse_count_body(message->raw + i, toread);
+    if (read != toread) {
+      print_error(message->raw, read);
+      abort();
+    }
+  }
+
+
+  read = parse_count_body(NULL, 0);
+  if (read != 0) {
+    print_error(message->raw, read);
+    abort();
+  }
+
+  if (num_messages != 1) {
+    printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
+    abort();
+  }
+
+  if(!message_eq(0, message)) abort();
+
+  parser_free();
+}
+
+void
+test_simple (const char *buf, enum http_errno err_expected)
+{
+  parser_init(HTTP_REQUEST);
+
+  enum http_errno err;
+
+  parse(buf, strlen(buf));
+  err = HTTP_PARSER_ERRNO(parser);
+  parse(NULL, 0);
+
+  parser_free();
+
+  /* In strict mode, allow us to pass with an unexpected HPE_STRICT as
+   * long as the caller isn't expecting success.
+   */
+#if HTTP_PARSER_STRICT
+  if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) {
+#else
+  if (err_expected != err) {
+#endif
+    fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n",
+        http_errno_name(err_expected), http_errno_name(err), buf);
+    abort();
+  }
+}
+
+void
+test_header_overflow_error (int req)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  const char *buf;
+  buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n";
+  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
+  assert(parsed == strlen(buf));
+
+  buf = "header-key: header-value\r\n";
+  size_t buflen = strlen(buf);
+
+  int i;
+  for (i = 0; i < 10000; i++) {
+    parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
+    if (parsed != buflen) {
+      //fprintf(stderr, "error found on iter %d\n", i);
+      assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW);
+      return;
+    }
+  }
+
+  fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n");
+  abort();
+}
+
+static void
+test_content_length_overflow (const char *buf, size_t buflen, int expect_ok)
+{
+  http_parser parser;
+  http_parser_init(&parser, HTTP_RESPONSE);
+  http_parser_execute(&parser, &settings_null, buf, buflen);
+
+  if (expect_ok)
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_OK);
+  else
+    assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_CONTENT_LENGTH);
+}
+
+void
+test_header_content_length_overflow_error (void)
+{
+#define X(size)                                                               \
+  "HTTP/1.1 200 OK\r\n"                                                       \
+  "Content-Length: " #size "\r\n"                                             \
+  "\r\n"
+  const char a[] = X(1844674407370955160);  /* 2^64 / 10 - 1 */
+  const char b[] = X(18446744073709551615); /* 2^64-1 */
+  const char c[] = X(18446744073709551616); /* 2^64   */
+#undef X
+  test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok      */
+  test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */
+  test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */
+}
+
+void
+test_chunk_content_length_overflow_error (void)
+{
+#define X(size)                                                               \
+    "HTTP/1.1 200 OK\r\n"                                                     \
+    "Transfer-Encoding: chunked\r\n"                                          \
+    "\r\n"                                                                    \
+    #size "\r\n"                                                              \
+    "..."
+  const char a[] = X(FFFFFFFFFFFFFFE);   /* 2^64 / 16 - 1 */
+  const char b[] = X(FFFFFFFFFFFFFFFF);  /* 2^64-1 */
+  const char c[] = X(10000000000000000); /* 2^64   */
+#undef X
+  test_content_length_overflow(a, sizeof(a) - 1, 1); /* expect ok      */
+  test_content_length_overflow(b, sizeof(b) - 1, 0); /* expect failure */
+  test_content_length_overflow(c, sizeof(c) - 1, 0); /* expect failure */
+}
+
+void
+test_no_overflow_long_body (int req, size_t length)
+{
+  http_parser parser;
+  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
+  size_t parsed;
+  size_t i;
+  char buf1[3000];
+  size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %lu\r\n\r\n",
+      req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", (unsigned long)length);
+  parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
+  if (parsed != buf1len)
+    goto err;
+
+  for (i = 0; i < length; i++) {
+    char foo = 'a';
+    parsed = http_parser_execute(&parser, &settings_null, &foo, 1);
+    if (parsed != 1)
+      goto err;
+  }
+
+  parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
+  if (parsed != buf1len) goto err;
+  return;
+
+ err:
+  fprintf(stderr,
+          "\n*** error in test_no_overflow_long_body %s of length %lu ***\n",
+          req ? "REQUEST" : "RESPONSE",
+          (unsigned long)length);
+  abort();
+}
+
+void
+test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
+{
+  int message_count = count_parsed_messages(3, r1, r2, r3);
+
+  char total[ strlen(r1->raw)
+            + strlen(r2->raw)
+            + strlen(r3->raw)
+            + 1
+            ];
+  total[0] = '\0';
+
+  strcat(total, r1->raw);
+  strcat(total, r2->raw);
+  strcat(total, r3->raw);
+
+  parser_init(r1->type);
+
+  size_t read;
+
+  read = parse(total, strlen(total));
+
+  if (parser->upgrade) {
+    upgrade_message_fix(total, read, 3, r1, r2, r3);
+    goto test;
+  }
+
+  if (read != strlen(total)) {
+    print_error(total, read);
+    abort();
+  }
+
+  read = parse(NULL, 0);
+
+  if (read != 0) {
+    print_error(total, read);
+    abort();
+  }
+
+test:
+
+  if (message_count != num_messages) {
+    fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages);
+    abort();
+  }
+
+  if (!message_eq(0, r1)) abort();
+  if (message_count > 1 && !message_eq(1, r2)) abort();
+  if (message_count > 2 && !message_eq(2, r3)) abort();
+
+  parser_free();
+}
+
+/* SCAN through every possible breaking to make sure the
+ * parser can handle getting the content in any chunks that
+ * might come from the socket
+ */
+void
+test_scan (const struct message *r1, const struct message *r2, const struct message *r3)
+{
+  char total[80*1024] = "\0";
+  char buf1[80*1024] = "\0";
+  char buf2[80*1024] = "\0";
+  char buf3[80*1024] = "\0";
+
+  strcat(total, r1->raw);
+  strcat(total, r2->raw);
+  strcat(total, r3->raw);
+
+  size_t read;
+
+  int total_len = strlen(total);
+
+  int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2;
+  int ops = 0 ;
+
+  size_t buf1_len, buf2_len, buf3_len;
+  int message_count = count_parsed_messages(3, r1, r2, r3);
+
+  int i,j,type_both;
+  for (type_both = 0; type_both < 2; type_both ++ ) {
+    for (j = 2; j < total_len; j ++ ) {
+      for (i = 1; i < j; i ++ ) {
+
+        if (ops % 1000 == 0)  {
+          printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
+          fflush(stdout);
+        }
+        ops += 1;
+
+        parser_init(type_both ? HTTP_BOTH : r1->type);
+
+        buf1_len = i;
+        strlncpy(buf1, sizeof(buf1), total, buf1_len);
+        buf1[buf1_len] = 0;
+
+        buf2_len = j - i;
+        strlncpy(buf2, sizeof(buf1), total+i, buf2_len);
+        buf2[buf2_len] = 0;
+
+        buf3_len = total_len - j;
+        strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
+        buf3[buf3_len] = 0;
+
+        read = parse(buf1, buf1_len);
+
+        if (parser->upgrade) goto test;
+
+        if (read != buf1_len) {
+          print_error(buf1, read);
+          goto error;
+        }
+
+        read += parse(buf2, buf2_len);
+
+        if (parser->upgrade) goto test;
+
+        if (read != buf1_len + buf2_len) {
+          print_error(buf2, read);
+          goto error;
+        }
+
+        read += parse(buf3, buf3_len);
+
+        if (parser->upgrade) goto test;
+
+        if (read != buf1_len + buf2_len + buf3_len) {
+          print_error(buf3, read);
+          goto error;
+        }
+
+        parse(NULL, 0);
+
+test:
+        if (parser->upgrade) {
+          upgrade_message_fix(total, read, 3, r1, r2, r3);
+        }
+
+        if (message_count != num_messages) {
+          fprintf(stderr, "\n\nParser didn't see %d messages only %d\n",
+            message_count, num_messages);
+          goto error;
+        }
+
+        if (!message_eq(0, r1)) {
+          fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n");
+          goto error;
+        }
+
+        if (message_count > 1 && !message_eq(1, r2)) {
+          fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n");
+          goto error;
+        }
+
+        if (message_count > 2 && !message_eq(2, r3)) {
+          fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
+          goto error;
+        }
+
+        parser_free();
+      }
+    }
+  }
+  puts("\b\b\b\b100%");
+  return;
+
+ error:
+  fprintf(stderr, "i=%d  j=%d\n", i, j);
+  fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1);
+  fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2);
+  fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3);
+  abort();
+}
+
+// user required to free the result
+// string terminated by \0
+char *
+create_large_chunked_message (int body_size_in_kb, const char* headers)
+{
+  int i;
+  size_t wrote = 0;
+  size_t headers_len = strlen(headers);
+  size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6;
+  char * buf = malloc(bufsize);
+
+  memcpy(buf, headers, headers_len);
+  wrote += headers_len;
+
+  for (i = 0; i < body_size_in_kb; i++) {
+    // write 1kb chunk into the body.
+    memcpy(buf + wrote, "400\r\n", 5);
+    wrote += 5;
+    memset(buf + wrote, 'C', 1024);
+    wrote += 1024;
+    strcpy(buf + wrote, "\r\n");
+    wrote += 2;
+  }
+
+  memcpy(buf + wrote, "0\r\n\r\n", 6);
+  wrote += 6;
+  assert(wrote == bufsize);
+
+  return buf;
+}
+
+/* Verify that we can pause parsing at any of the bytes in the
+ * message and still get the result that we're expecting. */
+void
+test_message_pause (const struct message *msg)
+{
+  char *buf = (char*) msg->raw;
+  size_t buflen = strlen(msg->raw);
+  size_t nread;
+
+  parser_init(msg->type);
+
+  do {
+    nread = parse_pause(buf, buflen);
+
+    // We can only set the upgrade buffer once we've gotten our message
+    // completion callback.
+    if (messages[0].message_complete_cb_called &&
+        msg->upgrade &&
+        parser->upgrade) {
+      messages[0].upgrade = buf + nread;
+      goto test;
+    }
+
+    if (nread < buflen) {
+
+      // Not much do to if we failed a strict-mode check
+      if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {
+        parser_free();
+        return;
+      }
+
+      assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);
+    }
+
+    buf += nread;
+    buflen -= nread;
+    http_parser_pause(parser, 0);
+  } while (buflen > 0);
+
+  nread = parse_pause(NULL, 0);
+  assert (nread == 0);
+
+test:
+  if (num_messages != 1) {
+    printf("\n*** num_messages != 1 after testing '%s' ***\n\n", msg->name);
+    abort();
+  }
+
+  if(!message_eq(0, msg)) abort();
+
+  parser_free();
+}
+
+int
+main (void)
+{
+  parser = NULL;
+  int i, j, k;
+  int request_count;
+  int response_count;
+  unsigned long version;
+  unsigned major;
+  unsigned minor;
+  unsigned patch;
+
+  version = http_parser_version();
+  major = (version >> 16) & 255;
+  minor = (version >> 8) & 255;
+  patch = version & 255;
+  printf("http_parser v%u.%u.%u (0x%06lx)\n", major, minor, patch, version);
+
+  printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
+
+  for (request_count = 0; requests[request_count].name; request_count++);
+  for (response_count = 0; responses[response_count].name; response_count++);
+
+  //// API
+  test_preserve_data();
+  test_parse_url();
+  test_method_str();
+
+  //// OVERFLOW CONDITIONS
+
+  test_header_overflow_error(HTTP_REQUEST);
+  test_no_overflow_long_body(HTTP_REQUEST, 1000);
+  test_no_overflow_long_body(HTTP_REQUEST, 100000);
+
+  test_header_overflow_error(HTTP_RESPONSE);
+  test_no_overflow_long_body(HTTP_RESPONSE, 1000);
+  test_no_overflow_long_body(HTTP_RESPONSE, 100000);
+
+  test_header_content_length_overflow_error();
+  test_chunk_content_length_overflow_error();
+
+  //// RESPONSES
+
+  for (i = 0; i < response_count; i++) {
+    test_message(&responses[i]);
+  }
+
+  for (i = 0; i < response_count; i++) {
+    test_message_pause(&responses[i]);
+  }
+
+  for (i = 0; i < response_count; i++) {
+    if (!responses[i].should_keep_alive) continue;
+    for (j = 0; j < response_count; j++) {
+      if (!responses[j].should_keep_alive) continue;
+      for (k = 0; k < response_count; k++) {
+        test_multiple3(&responses[i], &responses[j], &responses[k]);
+      }
+    }
+  }
+
+  test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);
+  test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);
+
+  // test very large chunked response
+  {
+    char * msg = create_large_chunked_message(31337,
+      "HTTP/1.0 200 OK\r\n"
+      "Transfer-Encoding: chunked\r\n"
+      "Content-Type: text/plain\r\n"
+      "\r\n");
+    struct message large_chunked =
+      {.name= "large chunked"
+      ,.type= HTTP_RESPONSE
+      ,.raw= msg
+      ,.should_keep_alive= FALSE
+      ,.message_complete_on_eof= FALSE
+      ,.http_major= 1
+      ,.http_minor= 0
+      ,.status_code= 200
+      ,.response_status= "OK"
+      ,.num_headers= 2
+      ,.headers=
+        { { "Transfer-Encoding", "chunked" }
+        , { "Content-Type", "text/plain" }
+        }
+      ,.body_size= 31337*1024
+      };
+    test_message_count_body(&large_chunked);
+    free(msg);
+  }
+
+
+
+  printf("response scan 1/2      ");
+  test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
+           , &responses[NO_BODY_HTTP10_KA_204]
+           , &responses[NO_REASON_PHRASE]
+           );
+
+  printf("response scan 2/2      ");
+  test_scan( &responses[BONJOUR_MADAME_FR]
+           , &responses[UNDERSTORE_HEADER_KEY]
+           , &responses[NO_CARRIAGE_RET]
+           );
+
+  puts("responses okay");
+
+
+  /// REQUESTS
+
+  test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
+
+  // Well-formed but incomplete
+  test_simple("GET / HTTP/1.1\r\n"
+              "Content-Type: text/plain\r\n"
+              "Content-Length: 6\r\n"
+              "\r\n"
+              "fooba",
+              HPE_OK);
+
+  static const char *all_methods[] = {
+    "DELETE",
+    "GET",
+    "HEAD",
+    "POST",
+    "PUT",
+    //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel
+    "OPTIONS",
+    "TRACE",
+    "COPY",
+    "LOCK",
+    "MKCOL",
+    "MOVE",
+    "PROPFIND",
+    "PROPPATCH",
+    "UNLOCK",
+    "REPORT",
+    "MKACTIVITY",
+    "CHECKOUT",
+    "MERGE",
+    "M-SEARCH",
+    "NOTIFY",
+    "SUBSCRIBE",
+    "UNSUBSCRIBE",
+    "PATCH",
+    0 };
+  const char **this_method;
+  for (this_method = all_methods; *this_method; this_method++) {
+    char buf[200];
+    sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
+    test_simple(buf, HPE_OK);
+  }
+
+  static const char *bad_methods[] = {
+      "ASDF",
+      "C******",
+      "COLA",
+      "GEM",
+      "GETA",
+      "M****",
+      "MKCOLA",
+      "PROPPATCHA",
+      "PUN",
+      "PX",
+      "SA",
+      "hello world",
+      0 };
+  for (this_method = bad_methods; *this_method; this_method++) {
+    char buf[200];
+    sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
+    test_simple(buf, HPE_INVALID_METHOD);
+  }
+
+  // illegal header field name line folding
+  test_simple("GET / HTTP/1.1\r\n"
+              "name\r\n"
+              " : value\r\n"
+              "\r\n",
+              HPE_INVALID_HEADER_TOKEN);
+
+  const char *dumbfuck2 =
+    "GET / HTTP/1.1\r\n"
+    "X-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n"
+    "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
+    "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
+    "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
+    "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
+    "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
+    "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
+    "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
+    "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
+    "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
+    "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
+    "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
+    "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
+    "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
+    "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
+    "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
+    "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
+    "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
+    "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
+    "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
+    "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
+    "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
+    "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
+    "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
+    "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
+    "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
+    "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
+    "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
+    "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
+    "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
+    "\tRA==\r\n"
+    "\t-----END CERTIFICATE-----\r\n"
+    "\r\n";
+  test_simple(dumbfuck2, HPE_OK);
+
+#if 0
+  // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body
+  // until EOF.
+  //
+  // no content-length
+  // error if there is a body without content length
+  const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
+                                           "Accept: */*\r\n"
+                                           "\r\n"
+                                           "HELLO";
+  test_simple(bad_get_no_headers_no_body, 0);
+#endif
+  /* TODO sending junk and large headers gets rejected */
+
+
+  /* check to make sure our predefined requests are okay */
+  for (i = 0; requests[i].name; i++) {
+    test_message(&requests[i]);
+  }
+
+  for (i = 0; i < request_count; i++) {
+    test_message_pause(&requests[i]);
+  }
+
+  for (i = 0; i < request_count; i++) {
+    if (!requests[i].should_keep_alive) continue;
+    for (j = 0; j < request_count; j++) {
+      if (!requests[j].should_keep_alive) continue;
+      for (k = 0; k < request_count; k++) {
+        test_multiple3(&requests[i], &requests[j], &requests[k]);
+      }
+    }
+  }
+
+  printf("request scan 1/4      ");
+  test_scan( &requests[GET_NO_HEADERS_NO_BODY]
+           , &requests[GET_ONE_HEADER_NO_BODY]
+           , &requests[GET_NO_HEADERS_NO_BODY]
+           );
+
+  printf("request scan 2/4      ");
+  test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]
+           , &requests[POST_IDENTITY_BODY_WORLD]
+           , &requests[GET_FUNKY_CONTENT_LENGTH]
+           );
+
+  printf("request scan 3/4      ");
+  test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
+           , &requests[CHUNKED_W_TRAILING_HEADERS]
+           , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
+           );
+
+  printf("request scan 4/4      ");
+  test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET]
+           , &requests[PREFIX_NEWLINE_GET ]
+           , &requests[CONNECT_REQUEST]
+           );
+
+  puts("requests okay");
+
+  return 0;
+}